@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,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summary Store — SQLite persistence for session summary records.
|
|
3
|
+
*
|
|
4
|
+
* Persists SessionSummaryRecord rows written by the autopilot engine after
|
|
5
|
+
* each summarise+consensus pipeline run. Uses better-sqlite3 with WAL journal
|
|
6
|
+
* mode, mirroring the pattern from terminal-store.ts.
|
|
7
|
+
*
|
|
8
|
+
* Database location: ~/.shooter/shooter.db (shared with terminal-store)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SessionSummaryRecord } from '$lib/types';
|
|
12
|
+
|
|
13
|
+
import Database from 'better-sqlite3';
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
|
|
17
|
+
// ── Constants ────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const DB_DIR = path.join(process.env.HOME || '', '.shooter');
|
|
20
|
+
const DB_PATH = path.join(DB_DIR, 'shooter.db');
|
|
21
|
+
|
|
22
|
+
// ── Column list ──────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const COLUMNS = [
|
|
25
|
+
'id',
|
|
26
|
+
'terminal_id',
|
|
27
|
+
'session_id',
|
|
28
|
+
'project_name',
|
|
29
|
+
'summary',
|
|
30
|
+
'next_steps',
|
|
31
|
+
'trigger',
|
|
32
|
+
'created_at',
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
// ── Row ↔ Record conversion ──────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export class SummaryStore {
|
|
38
|
+
private db: Database.Database;
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
fs.mkdirSync(DB_DIR, { recursive: true });
|
|
42
|
+
this.db = new Database(DB_PATH);
|
|
43
|
+
this.db.pragma('journal_mode = WAL');
|
|
44
|
+
this.db.exec(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS session_summaries (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
terminal_id TEXT,
|
|
48
|
+
session_id TEXT,
|
|
49
|
+
project_name TEXT,
|
|
50
|
+
summary TEXT NOT NULL,
|
|
51
|
+
next_steps TEXT NOT NULL DEFAULT '[]',
|
|
52
|
+
trigger TEXT NOT NULL,
|
|
53
|
+
created_at TEXT NOT NULL
|
|
54
|
+
)
|
|
55
|
+
`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
insert(record: SessionSummaryRecord): void {
|
|
59
|
+
const placeholders = COLUMNS.map(() => '?').join(', ');
|
|
60
|
+
this.db
|
|
61
|
+
.prepare(`INSERT INTO session_summaries (${COLUMNS.join(', ')}) VALUES (${placeholders})`)
|
|
62
|
+
.run(
|
|
63
|
+
record.id,
|
|
64
|
+
record.terminalId,
|
|
65
|
+
record.sessionId,
|
|
66
|
+
record.projectName,
|
|
67
|
+
record.summary,
|
|
68
|
+
record.nextSteps,
|
|
69
|
+
record.trigger,
|
|
70
|
+
record.createdAt
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
listRecent(limit: number, sessionId?: string): SessionSummaryRecord[] {
|
|
75
|
+
if (sessionId) {
|
|
76
|
+
const rows = this.db
|
|
77
|
+
.prepare(
|
|
78
|
+
'SELECT * FROM session_summaries WHERE session_id = ? ORDER BY created_at DESC LIMIT ?'
|
|
79
|
+
)
|
|
80
|
+
.all(sessionId, limit) as Record<string, unknown>[];
|
|
81
|
+
return rows.map(rowToRecord);
|
|
82
|
+
}
|
|
83
|
+
const rows = this.db
|
|
84
|
+
.prepare('SELECT * FROM session_summaries ORDER BY created_at DESC LIMIT ?')
|
|
85
|
+
.all(limit) as Record<string, unknown>[];
|
|
86
|
+
return rows.map(rowToRecord);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── SummaryStore ─────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
function rowToRecord(row: Record<string, unknown>): SessionSummaryRecord {
|
|
93
|
+
return {
|
|
94
|
+
createdAt: row.created_at as string,
|
|
95
|
+
id: row.id as string,
|
|
96
|
+
nextSteps: row.next_steps as string,
|
|
97
|
+
projectName: (row.project_name as string) ?? null,
|
|
98
|
+
sessionId: (row.session_id as string) ?? null,
|
|
99
|
+
summary: row.summary as string,
|
|
100
|
+
terminalId: (row.terminal_id as string) ?? null,
|
|
101
|
+
trigger: row.trigger as string,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Singleton ────────────────────────────────────────────────────────
|
|
106
|
+
// Shared instance across module loaders (same pattern as terminal-store).
|
|
107
|
+
|
|
108
|
+
const SS_GLOBAL_KEY = '__shooter_summary_store';
|
|
109
|
+
export const summaryStore: SummaryStore =
|
|
110
|
+
((globalThis as Record<string, unknown>)[SS_GLOBAL_KEY] as SummaryStore) || new SummaryStore();
|
|
111
|
+
(globalThis as Record<string, unknown>)[SS_GLOBAL_KEY] = summaryStore;
|
|
@@ -483,6 +483,12 @@ class PtyManager {
|
|
|
483
483
|
terminal.pty.resize(cols, rows);
|
|
484
484
|
terminal.cols = cols;
|
|
485
485
|
terminal.rows = rows;
|
|
486
|
+
// Broadcast the new PTY size so attached clients (e.g. view-only
|
|
487
|
+
// guests) can follow the terminal dimensions.
|
|
488
|
+
const msg = JSON.stringify({ cols, rows, type: 'resize' });
|
|
489
|
+
for (const ws of terminal.clients) {
|
|
490
|
+
this.safeSend(ws, msg);
|
|
491
|
+
}
|
|
486
492
|
return true;
|
|
487
493
|
} catch {
|
|
488
494
|
return false;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Resolves a terminal-scoped request to owner (API key) or guest (share token).
|
|
2
|
+
// Kept separate from auth.ts so routes without share semantics don't pull in SQLite.
|
|
3
|
+
|
|
4
|
+
import type { AccessContext } from '$lib/types';
|
|
5
|
+
|
|
6
|
+
import { validateAuth } from '../auth';
|
|
7
|
+
import { shareStore } from './share-store';
|
|
8
|
+
|
|
9
|
+
/** Extract the Bearer token from a request, or null. */
|
|
10
|
+
export function bearerToken(request: Request): null | string {
|
|
11
|
+
const auth = request.headers.get('Authorization') || request.headers.get('authorization');
|
|
12
|
+
if (!auth?.startsWith('Bearer ')) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return auth.slice(7).trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve access for a request targeting one terminal.
|
|
20
|
+
* Owner (valid API key) → { level: 'owner' }.
|
|
21
|
+
* Guest (valid share session for THIS terminal) → { level: 'guest', mode }.
|
|
22
|
+
* Anything else → null.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveAccess(request: Request, terminalId: string): AccessContext | null {
|
|
25
|
+
if (validateAuth(request) === null) {
|
|
26
|
+
return { level: 'owner', mode: null };
|
|
27
|
+
}
|
|
28
|
+
const token = bearerToken(request);
|
|
29
|
+
if (!token) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const session = shareStore.resolveToken(token);
|
|
33
|
+
if (session?.terminalId !== terminalId) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return { level: 'guest', mode: session.mode };
|
|
37
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Share Store — SQLite persistence for terminal sharing.
|
|
3
|
+
*
|
|
4
|
+
* terminal_shares: one share per terminal (scrypt password hash + mode).
|
|
5
|
+
* share_sessions: guest sessions keyed by sha256(token), 7-day TTL.
|
|
6
|
+
*
|
|
7
|
+
* Database location: ~/.shooter/shooter.db (same file as terminal-store).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ShareMode, TerminalShareRecord } from '$lib/types';
|
|
11
|
+
|
|
12
|
+
import Database from 'better-sqlite3';
|
|
13
|
+
import { createHash, randomBytes, scryptSync, timingSafeEqual } from 'crypto';
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
|
|
17
|
+
const DB_DIR = path.join(process.env.HOME || '', '.shooter');
|
|
18
|
+
const DB_PATH = path.join(DB_DIR, 'shooter.db');
|
|
19
|
+
|
|
20
|
+
const SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
21
|
+
|
|
22
|
+
// ── Password hashing (scrypt, per-share random salt) ─────────────────
|
|
23
|
+
|
|
24
|
+
export class ShareStore {
|
|
25
|
+
private db: Database.Database;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
fs.mkdirSync(DB_DIR, { recursive: true });
|
|
29
|
+
this.db = new Database(DB_PATH);
|
|
30
|
+
this.db.pragma('journal_mode = WAL');
|
|
31
|
+
|
|
32
|
+
this.db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS terminal_shares (
|
|
34
|
+
terminal_id TEXT PRIMARY KEY,
|
|
35
|
+
password_hash TEXT NOT NULL,
|
|
36
|
+
mode TEXT NOT NULL,
|
|
37
|
+
created_at INTEGER NOT NULL,
|
|
38
|
+
updated_at INTEGER NOT NULL
|
|
39
|
+
);
|
|
40
|
+
CREATE TABLE IF NOT EXISTS share_sessions (
|
|
41
|
+
token_hash TEXT PRIMARY KEY,
|
|
42
|
+
terminal_id TEXT NOT NULL,
|
|
43
|
+
created_at INTEGER NOT NULL,
|
|
44
|
+
expires_at INTEGER NOT NULL
|
|
45
|
+
)
|
|
46
|
+
`);
|
|
47
|
+
|
|
48
|
+
this.cleanup();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Purge expired sessions and shares whose terminal no longer exists. */
|
|
52
|
+
cleanup(): void {
|
|
53
|
+
this.db.prepare('DELETE FROM share_sessions WHERE expires_at < ?').run(Date.now());
|
|
54
|
+
try {
|
|
55
|
+
this.db
|
|
56
|
+
.prepare('DELETE FROM terminal_shares WHERE terminal_id NOT IN (SELECT id FROM terminals)')
|
|
57
|
+
.run();
|
|
58
|
+
this.db
|
|
59
|
+
.prepare('DELETE FROM share_sessions WHERE terminal_id NOT IN (SELECT id FROM terminals)')
|
|
60
|
+
.run();
|
|
61
|
+
} catch {
|
|
62
|
+
// terminals table may not exist yet on a fresh database — skip orphan cleanup.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Issue a new guest session for a shared terminal. Returns the raw token (stored hashed). */
|
|
67
|
+
createSession(terminalId: string): { expiresAt: number; token: string } {
|
|
68
|
+
const token = randomBytes(32).toString('hex');
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const expiresAt = now + SESSION_TTL_MS;
|
|
71
|
+
this.db
|
|
72
|
+
.prepare(
|
|
73
|
+
'INSERT INTO share_sessions (token_hash, terminal_id, created_at, expires_at) VALUES (?, ?, ?, ?)'
|
|
74
|
+
)
|
|
75
|
+
.run(hashToken(token), terminalId, now, expiresAt);
|
|
76
|
+
return { expiresAt, token };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Delete all guest sessions for a terminal (password change / revoke). */
|
|
80
|
+
deleteSessions(terminalId: string): void {
|
|
81
|
+
this.db.prepare('DELETE FROM share_sessions WHERE terminal_id = ?').run(terminalId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Revoke a share: delete the share row and every guest session for it. */
|
|
85
|
+
deleteShare(terminalId: string): void {
|
|
86
|
+
this.db.prepare('DELETE FROM terminal_shares WHERE terminal_id = ?').run(terminalId);
|
|
87
|
+
this.deleteSessions(terminalId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getShare(terminalId: string): null | TerminalShareRecord {
|
|
91
|
+
const row = this.db
|
|
92
|
+
.prepare('SELECT * FROM terminal_shares WHERE terminal_id = ?')
|
|
93
|
+
.get(terminalId) as Record<string, unknown> | undefined;
|
|
94
|
+
return row ? rowToShare(row) : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve a guest bearer token to its terminal + mode.
|
|
99
|
+
* Returns null if unknown, expired, or the share was revoked.
|
|
100
|
+
*/
|
|
101
|
+
resolveToken(token: string): null | { mode: ShareMode; terminalId: string } {
|
|
102
|
+
if (!token) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const row = this.db
|
|
106
|
+
.prepare(
|
|
107
|
+
`SELECT s.terminal_id, s.expires_at, sh.mode
|
|
108
|
+
FROM share_sessions s
|
|
109
|
+
JOIN terminal_shares sh ON sh.terminal_id = s.terminal_id
|
|
110
|
+
WHERE s.token_hash = ?`
|
|
111
|
+
)
|
|
112
|
+
.get(hashToken(token)) as Record<string, unknown> | undefined;
|
|
113
|
+
if (!row) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if ((row.expires_at as number) < Date.now()) {
|
|
117
|
+
this.db.prepare('DELETE FROM share_sessions WHERE token_hash = ?').run(hashToken(token));
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return { mode: row.mode as ShareMode, terminalId: row.terminal_id as string };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Create or replace the share for a terminal. */
|
|
124
|
+
setShare(record: TerminalShareRecord): void {
|
|
125
|
+
this.db
|
|
126
|
+
.prepare(
|
|
127
|
+
`INSERT OR REPLACE INTO terminal_shares
|
|
128
|
+
(terminal_id, password_hash, mode, created_at, updated_at)
|
|
129
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
130
|
+
)
|
|
131
|
+
.run(record.terminalId, record.passwordHash, record.mode, record.createdAt, record.updatedAt);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function hashPassword(password: string): string {
|
|
136
|
+
const salt = randomBytes(16).toString('hex');
|
|
137
|
+
const hash = scryptSync(password, salt, 64).toString('hex');
|
|
138
|
+
return `scrypt:${salt}:${hash}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function verifyPassword(password: string, stored: string): boolean {
|
|
142
|
+
const parts = stored.split(':');
|
|
143
|
+
if (parts.length !== 3 || parts[0] !== 'scrypt') {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const expected = Buffer.from(parts[2], 'hex');
|
|
147
|
+
const actual = scryptSync(password, parts[1], 64);
|
|
148
|
+
return expected.length === actual.length && timingSafeEqual(actual, expected);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Row mapping ──────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
function hashToken(token: string): string {
|
|
154
|
+
return createHash('sha256').update(token).digest('hex');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function rowToShare(row: Record<string, unknown>): TerminalShareRecord {
|
|
158
|
+
return {
|
|
159
|
+
createdAt: row.created_at as number,
|
|
160
|
+
mode: row.mode as ShareMode,
|
|
161
|
+
passwordHash: row.password_hash as string,
|
|
162
|
+
terminalId: row.terminal_id as string,
|
|
163
|
+
updatedAt: row.updated_at as number,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Singleton (globalThis bridges tsx server.ts + SvelteKit handler) ─
|
|
168
|
+
|
|
169
|
+
const SS_GLOBAL_KEY = '__shooter_share_store';
|
|
170
|
+
export const shareStore: ShareStore =
|
|
171
|
+
((globalThis as Record<string, unknown>)[SS_GLOBAL_KEY] as ShareStore) || new ShareStore();
|
|
172
|
+
(globalThis as Record<string, unknown>)[SS_GLOBAL_KEY] = shareStore;
|
|
@@ -13,6 +13,18 @@ const eventsClients: Set<WebSocket> =
|
|
|
13
13
|
((globalThis as Record<string, unknown>)[EVENTS_KEY] as Set<WebSocket>) || new Set<WebSocket>();
|
|
14
14
|
(globalThis as Record<string, unknown>)[EVENTS_KEY] = eventsClients;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Server-side listeners (e.g. the always-on autopilot engine) that observe
|
|
18
|
+
* every broadcast event. Kept on globalThis so the listener registered from the
|
|
19
|
+
* server.ts module graph and the broadcaster called from the bundled SvelteKit
|
|
20
|
+
* handler graph share one set.
|
|
21
|
+
*/
|
|
22
|
+
const LISTENERS_KEY = '__shooter_ws_event_listeners';
|
|
23
|
+
const eventListeners: Set<(event: ShooterEvent) => void> =
|
|
24
|
+
((globalThis as Record<string, unknown>)[LISTENERS_KEY] as Set<(event: ShooterEvent) => void>) ||
|
|
25
|
+
new Set<(event: ShooterEvent) => void>();
|
|
26
|
+
(globalThis as Record<string, unknown>)[LISTENERS_KEY] = eventListeners;
|
|
27
|
+
|
|
16
28
|
// ── Handlers ────────────────────────────────────────────────────────
|
|
17
29
|
|
|
18
30
|
/**
|
|
@@ -31,6 +43,15 @@ export function broadcastEvent(event: ShooterEvent): void {
|
|
|
31
43
|
ws.send(data);
|
|
32
44
|
}
|
|
33
45
|
}
|
|
46
|
+
|
|
47
|
+
// Fan out to server-side listeners (autopilot engine, etc.).
|
|
48
|
+
for (const listener of eventListeners) {
|
|
49
|
+
try {
|
|
50
|
+
listener(event);
|
|
51
|
+
} catch {
|
|
52
|
+
// a faulty listener must never break the broadcast
|
|
53
|
+
}
|
|
54
|
+
}
|
|
34
55
|
}
|
|
35
56
|
|
|
36
57
|
/**
|
|
@@ -62,3 +83,14 @@ export function handleEventsConnection(ws: WebSocket): void {
|
|
|
62
83
|
eventsClients.delete(ws);
|
|
63
84
|
});
|
|
64
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register a server-side listener that receives every broadcast ShooterEvent.
|
|
89
|
+
* Returns an unsubscribe function. Used by the always-on autopilot engine.
|
|
90
|
+
*/
|
|
91
|
+
export function onShooterEvent(listener: (event: ShooterEvent) => void): () => void {
|
|
92
|
+
eventListeners.add(listener);
|
|
93
|
+
return (): void => {
|
|
94
|
+
eventListeners.delete(listener);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Tracks WebSocket connections opened with a guest (scoped) ticket, per terminal,
|
|
2
|
+
// so revoke / mode-change / password-change can force-close them immediately.
|
|
3
|
+
// globalThis bridges the tsx server.ts and SvelteKit handler module scopes.
|
|
4
|
+
|
|
5
|
+
import type { WebSocket } from 'ws';
|
|
6
|
+
|
|
7
|
+
const GUESTS_KEY = '__shooter_ws_guest_conns';
|
|
8
|
+
const guests: Map<string, Set<WebSocket>> = ((globalThis as Record<string, unknown>)[
|
|
9
|
+
GUESTS_KEY
|
|
10
|
+
] as Map<string, Set<WebSocket>>) || new Map<string, Set<WebSocket>>();
|
|
11
|
+
(globalThis as Record<string, unknown>)[GUESTS_KEY] = guests;
|
|
12
|
+
|
|
13
|
+
/** Force-close every guest connection for a terminal. Returns the number closed. */
|
|
14
|
+
export function closeGuests(terminalId: string): number {
|
|
15
|
+
const set = guests.get(terminalId);
|
|
16
|
+
if (!set) {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
let closed = 0;
|
|
20
|
+
for (const ws of set) {
|
|
21
|
+
try {
|
|
22
|
+
ws.close(4001, 'Share revoked');
|
|
23
|
+
closed++;
|
|
24
|
+
} catch {
|
|
25
|
+
// Already closing/closed.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
guests.delete(terminalId);
|
|
29
|
+
return closed;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Register a guest connection; auto-removes itself on close. */
|
|
33
|
+
export function registerGuest(terminalId: string, ws: WebSocket): void {
|
|
34
|
+
let set = guests.get(terminalId);
|
|
35
|
+
if (!set) {
|
|
36
|
+
set = new Set<WebSocket>();
|
|
37
|
+
guests.set(terminalId, set);
|
|
38
|
+
}
|
|
39
|
+
set.add(ws);
|
|
40
|
+
ws.on('close', () => {
|
|
41
|
+
const current = guests.get(terminalId);
|
|
42
|
+
if (current) {
|
|
43
|
+
current.delete(ws);
|
|
44
|
+
if (current.size === 0) {
|
|
45
|
+
guests.delete(terminalId);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// /ws/session/:id -> Live structured session stream
|
|
6
6
|
// /ws/events -> Global event bus (broadcasts)
|
|
7
7
|
|
|
8
|
+
import type { TicketScope } from '$lib/types';
|
|
8
9
|
import type { IncomingMessage } from 'http';
|
|
9
10
|
import type { Duplex } from 'stream';
|
|
10
11
|
import type { WebSocket, WebSocketServer } from 'ws';
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
getEventsClientCount,
|
|
15
16
|
handleEventsConnection,
|
|
16
17
|
} from './events-handler.js';
|
|
18
|
+
import { registerGuest } from './guest-registry.js';
|
|
17
19
|
import { handleSessionConnection } from './session-handler.js';
|
|
18
20
|
import { handleSuperSessionConnection } from './super-session-handler.js';
|
|
19
21
|
import { handleTerminalConnection } from './terminal-handler.js';
|
|
@@ -49,7 +51,8 @@ export function setupWebSocketHandlers(
|
|
|
49
51
|
wss: WebSocketServer,
|
|
50
52
|
request: IncomingMessage,
|
|
51
53
|
socket: Duplex,
|
|
52
|
-
head: Buffer
|
|
54
|
+
head: Buffer,
|
|
55
|
+
scope?: TicketScope
|
|
53
56
|
): void {
|
|
54
57
|
const host = request.headers.host ?? 'localhost';
|
|
55
58
|
let pathname: string;
|
|
@@ -71,9 +74,25 @@ export function setupWebSocketHandlers(
|
|
|
71
74
|
return;
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
// Scoped (guest) tickets may only open the terminal/session channels of
|
|
78
|
+
// their own terminal. Events and super-session channels broadcast global
|
|
79
|
+
// data, so they are denied outright.
|
|
80
|
+
if (scope) {
|
|
81
|
+
const target = terminalMatch?.[1] ?? sessionMatch?.[1];
|
|
82
|
+
if (!target || superSessionMatch || target !== scope.terminalId) {
|
|
83
|
+
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
|
|
84
|
+
socket.destroy();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
74
89
|
wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {
|
|
75
90
|
allConnections.add(ws);
|
|
76
91
|
|
|
92
|
+
if (scope) {
|
|
93
|
+
registerGuest(scope.terminalId, ws);
|
|
94
|
+
}
|
|
95
|
+
|
|
77
96
|
ws.on('close', () => {
|
|
78
97
|
allConnections.delete(ws);
|
|
79
98
|
});
|
|
@@ -84,13 +103,13 @@ export function setupWebSocketHandlers(
|
|
|
84
103
|
|
|
85
104
|
if (terminalMatch) {
|
|
86
105
|
const terminalId = terminalMatch[1];
|
|
87
|
-
handleTerminalConnection(ws, terminalId);
|
|
106
|
+
handleTerminalConnection(ws, terminalId, scope);
|
|
88
107
|
} else if (superSessionMatch) {
|
|
89
108
|
const superSessionId = superSessionMatch[1];
|
|
90
109
|
handleSuperSessionConnection(ws, superSessionId);
|
|
91
110
|
} else if (sessionMatch) {
|
|
92
111
|
const sessionId = sessionMatch[1];
|
|
93
|
-
handleSessionConnection(ws, sessionId);
|
|
112
|
+
handleSessionConnection(ws, sessionId, scope);
|
|
94
113
|
} else if (isEvents) {
|
|
95
114
|
handleEventsConnection(ws);
|
|
96
115
|
}
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
WireSessionServerMessage as ServerMessage,
|
|
17
17
|
SessionWatcherLike,
|
|
18
18
|
TextContentBlock,
|
|
19
|
+
TicketScope,
|
|
19
20
|
} from '$lib/types';
|
|
20
21
|
import type { WebSocket } from 'ws';
|
|
21
22
|
|
|
@@ -41,7 +42,7 @@ let _sessionWatcher: null | SessionWatcherLike = null;
|
|
|
41
42
|
* 3. If still no terminal, treat as an external session — find the JSONL
|
|
42
43
|
* file directly and stream it via the session watcher.
|
|
43
44
|
*/
|
|
44
|
-
export function handleSessionConnection(ws: WebSocket, id: string): void {
|
|
45
|
+
export function handleSessionConnection(ws: WebSocket, id: string, scope?: TicketScope): void {
|
|
45
46
|
const state: ConnectionState = {
|
|
46
47
|
isExternalSession: false,
|
|
47
48
|
retryInterval: null,
|
|
@@ -66,7 +67,7 @@ export function handleSessionConnection(ws: WebSocket, id: string): void {
|
|
|
66
67
|
if (terminal) {
|
|
67
68
|
state.terminalId = terminal.id;
|
|
68
69
|
subscribeToSession(ws, state, terminal);
|
|
69
|
-
wireClientMessages(ws, state);
|
|
70
|
+
wireClientMessages(ws, state, scope);
|
|
70
71
|
wireCleanup(ws, state);
|
|
71
72
|
return;
|
|
72
73
|
}
|
|
@@ -76,7 +77,7 @@ export function handleSessionConnection(ws: WebSocket, id: string): void {
|
|
|
76
77
|
if (jsonlPath) {
|
|
77
78
|
state.isExternalSession = true;
|
|
78
79
|
subscribeToExternalSession(ws, state, jsonlPath);
|
|
79
|
-
wireClientMessages(ws, state);
|
|
80
|
+
wireClientMessages(ws, state, scope);
|
|
80
81
|
wireCleanup(ws, state);
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
@@ -507,7 +508,7 @@ function wireCleanup(ws: WebSocket, state: ConnectionState): void {
|
|
|
507
508
|
* Extracted so both terminal-backed and external sessions share the same
|
|
508
509
|
* message loop.
|
|
509
510
|
*/
|
|
510
|
-
function wireClientMessages(ws: WebSocket, state: ConnectionState): void {
|
|
511
|
+
function wireClientMessages(ws: WebSocket, state: ConnectionState, scope?: TicketScope): void {
|
|
511
512
|
ws.on('message', (raw: Buffer | string) => {
|
|
512
513
|
const data = typeof raw === 'string' ? raw : raw.toString('utf-8');
|
|
513
514
|
const msg = parseClientMessage(data);
|
|
@@ -518,6 +519,10 @@ function wireClientMessages(ws: WebSocket, state: ConnectionState): void {
|
|
|
518
519
|
try {
|
|
519
520
|
switch (msg.type) {
|
|
520
521
|
case 'cancel': {
|
|
522
|
+
if (scope?.readOnly) {
|
|
523
|
+
safeSend(ws, { message: 'This shared terminal is view-only.', type: 'error' });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
521
526
|
if (state.isExternalSession) {
|
|
522
527
|
safeSend(ws, {
|
|
523
528
|
message: 'Cannot cancel — this is a read-only session. Connect to a terminal first.',
|
|
@@ -535,6 +540,10 @@ function wireClientMessages(ws: WebSocket, state: ConnectionState): void {
|
|
|
535
540
|
}
|
|
536
541
|
|
|
537
542
|
case 'send-input': {
|
|
543
|
+
if (scope?.readOnly) {
|
|
544
|
+
safeSend(ws, { message: 'This shared terminal is view-only.', type: 'error' });
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
538
547
|
if (state.isExternalSession) {
|
|
539
548
|
safeSend(ws, {
|
|
540
549
|
message:
|
|
@@ -557,6 +566,11 @@ function wireClientMessages(ws: WebSocket, state: ConnectionState): void {
|
|
|
557
566
|
}
|
|
558
567
|
|
|
559
568
|
case 'subscribe': {
|
|
569
|
+
// Scoped guests may only (re)subscribe to their own terminal's session.
|
|
570
|
+
if (scope && msg.sessionId !== scope.terminalId) {
|
|
571
|
+
safeSend(ws, { message: 'Not authorized for this session.', type: 'error' });
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
560
574
|
// (Re)subscribe to a different session. Clean up the old subscription.
|
|
561
575
|
if (state.retryInterval) {
|
|
562
576
|
clearInterval(state.retryInterval);
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
TerminalPtyManagerLike as PtyManagerLike,
|
|
8
8
|
WireTerminalServerMessage as ServerMessage,
|
|
9
9
|
TerminalSignal,
|
|
10
|
+
TicketScope,
|
|
10
11
|
} from '$lib/types';
|
|
11
12
|
import type { WebSocket } from 'ws';
|
|
12
13
|
|
|
@@ -28,7 +29,11 @@ let _ptyManager: null | PtyManagerLike = null;
|
|
|
28
29
|
* Attaches the client to the terminal's viewer set, replays scrollback,
|
|
29
30
|
* and relays PTY I/O bidirectionally.
|
|
30
31
|
*/
|
|
31
|
-
export function handleTerminalConnection(
|
|
32
|
+
export function handleTerminalConnection(
|
|
33
|
+
ws: WebSocket,
|
|
34
|
+
terminalId: string,
|
|
35
|
+
scope?: TicketScope
|
|
36
|
+
): void {
|
|
32
37
|
// ── 1. Look up the terminal ──────────────────────────────────────
|
|
33
38
|
if (!_ptyManager) {
|
|
34
39
|
safeSend(ws, { message: 'PTY manager not initialised', type: 'error' });
|
|
@@ -62,6 +67,11 @@ export function handleTerminalConnection(ws: WebSocket, terminalId: string): voi
|
|
|
62
67
|
return; // Silently ignore malformed messages.
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
// View-only guests: every inbound frame type mutates the PTY — drop them all.
|
|
71
|
+
if (scope?.readOnly) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
// Don't allow input to exited terminals.
|
|
66
76
|
if (terminal.status === 'exited') {
|
|
67
77
|
safeSend(ws, { message: 'Terminal has exited', type: 'error' });
|
|
@@ -74,9 +84,18 @@ export function handleTerminalConnection(ws: WebSocket, terminalId: string): voi
|
|
|
74
84
|
terminal.pty.write(msg.data);
|
|
75
85
|
break;
|
|
76
86
|
|
|
77
|
-
case 'resize':
|
|
87
|
+
case 'resize': {
|
|
78
88
|
terminal.pty.resize(msg.cols, msg.rows);
|
|
89
|
+
// Broadcast the new PTY size to the other attached clients so
|
|
90
|
+
// view-only guests can follow the owner's terminal dimensions.
|
|
91
|
+
const resizeMsg: ServerMessage = { cols: msg.cols, rows: msg.rows, type: 'resize' };
|
|
92
|
+
for (const client of terminal.clients) {
|
|
93
|
+
if (client !== ws) {
|
|
94
|
+
safeSend(client, resizeMsg);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
79
97
|
break;
|
|
98
|
+
}
|
|
80
99
|
|
|
81
100
|
case 'signal': {
|
|
82
101
|
if (msg.signal === 'SIGINT') {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// This avoids putting the long-lived API_KEY in WebSocket URL query parameters,
|
|
6
6
|
// which would appear in proxy logs, Cloudflare access logs, and browser history.
|
|
7
7
|
|
|
8
|
-
import type { Ticket } from '$lib/types';
|
|
8
|
+
import type { Ticket, TicketScope } from '$lib/types';
|
|
9
9
|
|
|
10
10
|
import { randomBytes } from 'crypto';
|
|
11
11
|
|
|
@@ -21,32 +21,40 @@ const tickets: Map<string, Ticket> =
|
|
|
21
21
|
/**
|
|
22
22
|
* Generate a new single-use ticket (32-byte hex string).
|
|
23
23
|
* The ticket is valid for 30 seconds and can only be consumed once.
|
|
24
|
+
* An optional scope restricts the ticket to one terminal's channels
|
|
25
|
+
* (and optionally to read-only access).
|
|
24
26
|
*/
|
|
25
|
-
export function generateTicket(): string {
|
|
27
|
+
export function generateTicket(scope?: TicketScope): string {
|
|
26
28
|
const ticket = randomBytes(32).toString('hex');
|
|
27
|
-
tickets.set(ticket, {
|
|
29
|
+
tickets.set(ticket, {
|
|
30
|
+
createdAt: Date.now(),
|
|
31
|
+
readOnly: scope?.readOnly ?? null,
|
|
32
|
+
terminalId: scope?.terminalId ?? null,
|
|
33
|
+
used: false,
|
|
34
|
+
});
|
|
28
35
|
return ticket;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
/**
|
|
32
39
|
* Validate and consume a ticket.
|
|
33
|
-
* Returns
|
|
34
|
-
* A valid ticket is marked as used
|
|
40
|
+
* Returns the consumed Ticket (including any scope) if it is valid, not yet
|
|
41
|
+
* used, and not expired; otherwise null. A valid ticket is marked as used
|
|
42
|
+
* (single-use) and cannot be reused.
|
|
35
43
|
*/
|
|
36
|
-
export function validateTicket(ticket: null | string):
|
|
44
|
+
export function validateTicket(ticket: null | string): null | Ticket {
|
|
37
45
|
if (!ticket) {
|
|
38
|
-
return
|
|
46
|
+
return null;
|
|
39
47
|
}
|
|
40
48
|
const entry = tickets.get(ticket);
|
|
41
49
|
if (!entry || entry.used) {
|
|
42
|
-
return
|
|
50
|
+
return null;
|
|
43
51
|
}
|
|
44
52
|
if (Date.now() - entry.createdAt > 30_000) {
|
|
45
53
|
tickets.delete(ticket);
|
|
46
|
-
return
|
|
54
|
+
return null;
|
|
47
55
|
}
|
|
48
56
|
entry.used = true;
|
|
49
|
-
return
|
|
57
|
+
return entry;
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
// Cleanup expired tickets every 30 seconds (matches ticket lifetime).
|