@juspay/shooter 1.17.0 → 1.19.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/{0.B0O0vCnX.css → 0.BwNtE8TX.css} +1 -1
- package/build/client/_app/immutable/assets/0.BwNtE8TX.css.br +0 -0
- package/build/client/_app/immutable/assets/{0.B0O0vCnX.css.gz → 0.BwNtE8TX.css.gz} +0 -0
- package/build/client/_app/immutable/assets/8.BYgAX7hR.css +1 -0
- package/build/client/_app/immutable/assets/8.BYgAX7hR.css.br +0 -0
- package/build/client/_app/immutable/assets/8.BYgAX7hR.css.gz +0 -0
- package/build/client/_app/immutable/assets/9.DV6pZunn.css +1 -0
- package/build/client/_app/immutable/assets/9.DV6pZunn.css.br +0 -0
- package/build/client/_app/immutable/assets/9.DV6pZunn.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{DZQMsHM5.js → 2rBV5OkJ.js} +1 -1
- package/build/client/_app/immutable/chunks/2rBV5OkJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/2rBV5OkJ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BB2l8o4X.js +1 -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/{Cg3dlX05.js → BPDiEZo0.js} +2 -2
- package/build/client/_app/immutable/chunks/BPDiEZo0.js.br +0 -0
- package/build/client/_app/immutable/chunks/BPDiEZo0.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{C_9BZILB.js → BcpydfqI.js} +1 -1
- package/build/client/_app/immutable/chunks/BcpydfqI.js.br +0 -0
- package/build/client/_app/immutable/chunks/BcpydfqI.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BcqA7eKM.js +3 -0
- 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/BdtLzPpO.js +1 -0
- package/build/client/_app/immutable/chunks/BdtLzPpO.js.br +0 -0
- package/build/client/_app/immutable/chunks/BdtLzPpO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BRqaaL5D.js → BvmdJful.js} +1 -1
- 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/{C5VOyQCG.js → ClIPTXf3.js} +1 -1
- 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/{BctvtE4d.js → DA4Zt9Me.js} +1 -1
- package/build/client/_app/immutable/chunks/DA4Zt9Me.js.br +0 -0
- package/build/client/_app/immutable/chunks/{BctvtE4d.js.gz → DA4Zt9Me.js.gz} +0 -0
- package/build/client/_app/immutable/chunks/{CjfxuHdN.js → DCDL_9ys.js} +1 -1
- 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/{DYuMZGL5.js → DWmC0QM7.js} +1 -1
- package/build/client/_app/immutable/chunks/DWmC0QM7.js.br +0 -0
- package/build/client/_app/immutable/chunks/DWmC0QM7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DZvnhU_8.js → ZS5XYDx_.js} +2 -2
- package/build/client/_app/immutable/chunks/ZS5XYDx_.js.br +0 -0
- package/build/client/_app/immutable/chunks/ZS5XYDx_.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.D4TXlu7A.js +2 -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 +1 -0
- 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 +10 -0
- 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.Fqso94b3.js → 1.BVnLUSs-.js} +1 -1
- 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/{8.BjKgvSie.js → 10.D1wl2wPX.js} +2 -2
- 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/{9.BRT6HOXB.js → 11.C18nMGmp.js} +1 -1
- 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.BusCVJWk.js → 2.D1Mm0DUX.js} +2 -2
- 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.DUlpocIc.js → 3.Wfz3TcJd.js} +3 -3
- 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.BSVqdrrD.js → 4.CBX9A3ka.js} +2 -2
- 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.Cfj35gpY.js → 5.DIVKuZc9.js} +1 -1
- 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.CG4eKRH0.js → 6.DtZAEPXb.js} +1 -1
- 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.DHilxD1o.js → 7.MfBRh32I.js} +1 -1
- 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 +1 -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 +2 -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/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-BWFSL107.js → 0-DJqyZZTr.js} +4 -4
- package/build/server/chunks/{0-BWFSL107.js.map → 0-DJqyZZTr.js.map} +1 -1
- package/build/server/chunks/1-2YUVen1F.js +9 -0
- package/build/server/chunks/{1-Bw5KlAjL.js.map → 1-2YUVen1F.js.map} +1 -1
- package/build/server/chunks/10-D1X7LB3v.js +9 -0
- package/build/server/chunks/10-D1X7LB3v.js.map +1 -0
- package/build/server/chunks/11-qXSPdF5j.js +9 -0
- package/build/server/chunks/11-qXSPdF5j.js.map +1 -0
- package/build/server/chunks/{2-CQ3yYSVK.js → 2-BD7kj1mt.js} +3 -3
- package/build/server/chunks/{2-CQ3yYSVK.js.map → 2-BD7kj1mt.js.map} +1 -1
- package/build/server/chunks/{3-DZ4H9hPs.js → 3-oNjv-BhZ.js} +3 -3
- package/build/server/chunks/{3-DZ4H9hPs.js.map → 3-oNjv-BhZ.js.map} +1 -1
- package/build/server/chunks/{4-BtYdKCVW.js → 4-Bb5VFhsO.js} +3 -3
- package/build/server/chunks/{4-BtYdKCVW.js.map → 4-Bb5VFhsO.js.map} +1 -1
- package/build/server/chunks/{5-CvJK3PiH.js → 5-oNoWuIsn.js} +3 -3
- package/build/server/chunks/{5-CvJK3PiH.js.map → 5-oNoWuIsn.js.map} +1 -1
- package/build/server/chunks/6-DRJGUqHG.js +9 -0
- package/build/server/chunks/{6-BZ0enR6b.js.map → 6-DRJGUqHG.js.map} +1 -1
- package/build/server/chunks/7-_giJiu0L.js +9 -0
- package/build/server/chunks/{7-Lg8imTZn.js.map → 7-_giJiu0L.js.map} +1 -1
- package/build/server/chunks/8-zvWAVNT5.js +9 -0
- package/build/server/chunks/8-zvWAVNT5.js.map +1 -0
- package/build/server/chunks/9-DVyDL445.js +9 -0
- package/build/server/chunks/9-DVyDL445.js.map +1 -0
- package/build/server/chunks/Banner-BgaAs1rs.js +90 -0
- package/build/server/chunks/Banner-BgaAs1rs.js.map +1 -0
- package/build/server/chunks/{Button-B5dU-ntz.js → Button-D0hZ7JYt.js} +2 -2
- package/build/server/chunks/Button-D0hZ7JYt.js.map +1 -0
- package/build/server/chunks/{Icon-C7Ml3GX6.js → Icon-D0GBnDcs.js} +3 -3
- package/build/server/chunks/Icon-D0GBnDcs.js.map +1 -0
- package/build/server/chunks/{Input-CPGO0sbS.js → Input-OmIiydSx.js} +2 -2
- package/build/server/chunks/Input-OmIiydSx.js.map +1 -0
- package/build/server/chunks/{Pill-CcrtCejm.js → Pill-4xJ-VhAA.js} +3 -3
- package/build/server/chunks/Pill-4xJ-VhAA.js.map +1 -0
- package/build/server/chunks/{Shimmer-C5jkvGr1.js → Shimmer-Dw2uvTC1.js} +2 -2
- package/build/server/chunks/Shimmer-Dw2uvTC1.js.map +1 -0
- package/build/server/chunks/{_error.svelte-CSIxs-ab.js → _error.svelte-CZnkxeLr.js} +8 -8
- package/build/server/chunks/{_error.svelte-CSIxs-ab.js.map → _error.svelte-CZnkxeLr.js.map} +1 -1
- package/build/server/chunks/{_layout.svelte-noB4j-v2.js → _layout.svelte-DfgNGGiM.js} +16 -11
- package/build/server/chunks/_layout.svelte-DfgNGGiM.js.map +1 -0
- package/build/server/chunks/{_page.svelte-DnTpPnPR.js → _page.svelte-BLo2v_8E.js} +7 -88
- package/build/server/chunks/_page.svelte-BLo2v_8E.js.map +1 -0
- package/build/server/chunks/_page.svelte-BTlfUsBp.js +43 -0
- package/build/server/chunks/_page.svelte-BTlfUsBp.js.map +1 -0
- package/build/server/chunks/{_page.svelte-C60lAagP.js → _page.svelte-BUBLUSGo.js} +8 -8
- package/build/server/chunks/_page.svelte-BUBLUSGo.js.map +1 -0
- package/build/server/chunks/{_page.svelte-BV0XyYJZ.js → _page.svelte-BX2FMgSg.js} +4 -4
- package/build/server/chunks/{_page.svelte-BV0XyYJZ.js.map → _page.svelte-BX2FMgSg.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-BUkm2304.js → _page.svelte-C7B0qdrC.js} +5 -5
- package/build/server/chunks/{_page.svelte-BUkm2304.js.map → _page.svelte-C7B0qdrC.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-Dmg-RFCg.js → _page.svelte-CE7COWnF.js} +7 -7
- package/build/server/chunks/{_page.svelte-Dmg-RFCg.js.map → _page.svelte-CE7COWnF.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-BfB8maoc.js → _page.svelte-CWsjjd4l.js} +9 -9
- package/build/server/chunks/{_page.svelte-BfB8maoc.js.map → _page.svelte-CWsjjd4l.js.map} +1 -1
- package/build/server/chunks/_page.svelte-D5S2hkBk.js +104 -0
- package/build/server/chunks/_page.svelte-D5S2hkBk.js.map +1 -0
- package/build/server/chunks/{_page.svelte-B6qyh-K-.js → _page.svelte-D_Ey8QRG.js} +11 -11
- package/build/server/chunks/{_page.svelte-B6qyh-K-.js.map → _page.svelte-D_Ey8QRG.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-DuzZr5dA.js → _page.svelte-tBuIq8Pg.js} +11 -11
- package/build/server/chunks/{_page.svelte-DuzZr5dA.js.map → _page.svelte-tBuIq8Pg.js.map} +1 -1
- package/build/server/chunks/_server.ts-BaaY7Z9D.js +77 -0
- package/build/server/chunks/_server.ts-BaaY7Z9D.js.map +1 -0
- package/build/server/chunks/{_server.ts-5wx4ZppI.js → _server.ts-Bi0Oe4PF.js} +7 -4
- package/build/server/chunks/_server.ts-Bi0Oe4PF.js.map +1 -0
- package/build/server/chunks/_server.ts-C0317RBD.js +57 -0
- package/build/server/chunks/_server.ts-C0317RBD.js.map +1 -0
- package/build/server/chunks/{_server.ts-CKXVBbwb.js → _server.ts-CRVNEOd2.js} +10 -8
- package/build/server/chunks/_server.ts-CRVNEOd2.js.map +1 -0
- package/build/server/chunks/_server.ts-CVPZOpiv.js +23 -0
- package/build/server/chunks/_server.ts-CVPZOpiv.js.map +1 -0
- package/build/server/chunks/{_server.ts-CyjDrcZN.js → _server.ts-C_OOUqsd.js} +9 -1
- package/build/server/chunks/_server.ts-C_OOUqsd.js.map +1 -0
- package/build/server/chunks/{_server.ts-BMMTS86y.js → _server.ts-D9ir7u24.js} +3 -4
- package/build/server/chunks/{_server.ts-BMMTS86y.js.map → _server.ts-D9ir7u24.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DZ5naqSL.js → _server.ts-DMm0hBP4.js} +5 -1
- package/build/server/chunks/_server.ts-DMm0hBP4.js.map +1 -0
- package/build/server/chunks/{_server.ts-CgHc1Zpx.js → _server.ts-DhJx0DLr.js} +7 -4
- package/build/server/chunks/_server.ts-DhJx0DLr.js.map +1 -0
- package/build/server/chunks/_server.ts-DkZX_O9a.js +39 -0
- package/build/server/chunks/_server.ts-DkZX_O9a.js.map +1 -0
- package/build/server/chunks/{_server.ts-B1z0q6qZ.js → _server.ts-DxT9IlZF.js} +6 -5
- package/build/server/chunks/_server.ts-DxT9IlZF.js.map +1 -0
- package/build/server/chunks/{_server.ts-Bt7EAfjo.js → _server.ts-MbnroWEF.js} +25 -48
- package/build/server/chunks/_server.ts-MbnroWEF.js.map +1 -0
- package/build/server/chunks/_server.ts-Mttr0-Sl.js +48 -0
- package/build/server/chunks/_server.ts-Mttr0-Sl.js.map +1 -0
- package/build/server/chunks/_server.ts-jtqWDWcf.js +45 -0
- package/build/server/chunks/_server.ts-jtqWDWcf.js.map +1 -0
- package/build/server/chunks/{cache-Me3zUAaD.js → cache-BlMaDsHi.js} +2 -2
- package/build/server/chunks/{cache-Me3zUAaD.js.map → cache-BlMaDsHi.js.map} +1 -1
- package/build/server/chunks/{client-CfNnl32g.js → client-Ds1brw-8.js} +4 -4
- package/build/server/chunks/{client-CfNnl32g.js.map → client-Ds1brw-8.js.map} +1 -1
- package/build/server/chunks/client2-DngLdcUc.js +7 -0
- package/build/server/chunks/{client2-DDP30_vY.js.map → client2-DngLdcUc.js.map} +1 -1
- package/build/server/chunks/coordinator-DMU_ADXf.js +530 -0
- package/build/server/chunks/coordinator-DMU_ADXf.js.map +1 -0
- package/build/server/chunks/{index-CJrGuxuM.js → index-CoYB03g7.js} +2 -2
- package/build/server/chunks/{index-CJrGuxuM.js.map → index-CoYB03g7.js.map} +1 -1
- package/build/server/chunks/{index-server--49oHtA0.js → index-server-Bq3cnK69.js} +2 -2
- package/build/server/chunks/{index-server--49oHtA0.js.map → index-server-Bq3cnK69.js.map} +1 -1
- package/build/server/chunks/{index2-MY7PXeAc.js → index2-dSGQ9Eaa.js} +2 -2
- package/build/server/chunks/{index2-MY7PXeAc.js.map → index2-dSGQ9Eaa.js.map} +1 -1
- package/build/server/chunks/{pty-manager-RmhVe2Ez.js → pty-manager-41h3IK8K.js} +100 -2
- package/build/server/chunks/pty-manager-41h3IK8K.js.map +1 -0
- package/build/server/chunks/qwen-reader-DGfUbKaJ.js +2112 -0
- package/build/server/chunks/qwen-reader-DGfUbKaJ.js.map +1 -0
- package/build/server/chunks/{registry-DzJj2E6I.js → registry-D4J_CuzW.js} +56 -24
- package/build/server/chunks/registry-D4J_CuzW.js.map +1 -0
- package/build/server/chunks/{root-xvQIR1Bt.js → root-D4IoFC8F.js} +2 -2
- package/build/server/chunks/root-D4IoFC8F.js.map +1 -0
- package/build/server/chunks/{state.svelte-RCtlkrNH.js → state.svelte-CmHqngc_.js} +3 -3
- package/build/server/chunks/{state.svelte-RCtlkrNH.js.map → state.svelte-CmHqngc_.js.map} +1 -1
- package/build/server/chunks/{stores-C-LqoonT.js → stores-CRYxfF0o.js} +4 -4
- package/build/server/chunks/stores-CRYxfF0o.js.map +1 -0
- package/build/server/chunks/super-session-handler-DPyxFgmz.js +22 -0
- package/build/server/chunks/super-session-handler-DPyxFgmz.js.map +1 -0
- package/build/server/index.js +4 -4
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +79 -21
- package/build/server/manifest.js.map +1 -1
- package/package.json +2 -2
- package/scripts/e2e-all-features.sh +204 -0
- package/scripts/e2e-cross-terminal.sh +168 -0
- package/server.ts +37 -0
- package/src/lib/modules/client/common/provider.ts +0 -2
- package/src/lib/modules/client/terminal/ChatView.svelte +9 -2
- package/src/lib/modules/client/terminal/LaunchSheet.svelte +3 -0
- package/src/lib/modules/server/sessions/amp-reader.ts +439 -0
- package/src/lib/modules/server/sessions/copilot-reader.ts +542 -0
- package/src/lib/modules/server/sessions/cursor-reader.ts +634 -0
- package/src/lib/modules/server/sessions/gemini-reader.ts +48 -25
- package/src/lib/modules/server/sessions/opencode-reader.ts +13 -12
- package/src/lib/modules/server/sessions/process-detector.ts +37 -60
- package/src/lib/modules/server/sessions/provider-paths.ts +173 -0
- package/src/lib/modules/server/sessions/qwen-reader.ts +41 -15
- package/src/lib/modules/server/sessions/registry.ts +55 -14
- package/src/lib/modules/server/sos/coordinator.ts +492 -0
- package/src/lib/modules/server/sos/policy-gate.ts +56 -0
- package/src/lib/modules/server/sos/relay-store.ts +159 -0
- package/src/lib/modules/server/terminal/generic-session-watcher.ts +163 -0
- package/src/lib/modules/server/terminal/pty-input.ts +37 -0
- package/src/lib/modules/server/terminal/pty-manager.ts +51 -0
- package/src/lib/modules/server/ws/server.ts +6 -1
- package/src/lib/modules/server/ws/session-handler.ts +17 -2
- package/src/lib/modules/server/ws/super-session-handler.ts +200 -0
- package/src/lib/theme.css +1 -2
- package/src/lib/types/generated/Sessions.ts +1 -4
- package/src/lib/types/index.ts +2 -1
- package/src/lib/types/server.ts +23 -6
- package/src/lib/types/sessions.ts +1 -10
- package/src/lib/types/sos.ts +134 -0
- package/src/routes/+layout.svelte +9 -2
- package/src/routes/api/sessions/connect/+server.ts +7 -3
- package/src/routes/api/sos/+server.ts +36 -0
- package/src/routes/api/sos/[id]/+server.ts +55 -0
- package/src/routes/api/sos/[id]/inject/+server.ts +44 -0
- package/src/routes/api/sos/[id]/members/+server.ts +47 -0
- package/src/routes/api/sos/[id]/members/[mid]/+server.ts +17 -0
- package/src/routes/api/sos/[id]/rules/+server.ts +85 -0
- package/src/routes/sos/+page.svelte +195 -0
- package/src/routes/sos/[id]/+page.svelte +677 -0
- package/build/client/_app/immutable/assets/0.B0O0vCnX.css.br +0 -0
- package/build/client/_app/immutable/chunks/BRqaaL5D.js.br +0 -0
- package/build/client/_app/immutable/chunks/BRqaaL5D.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BctvtE4d.js.br +0 -0
- package/build/client/_app/immutable/chunks/BxFShcQO.js +0 -1
- package/build/client/_app/immutable/chunks/BxFShcQO.js.br +0 -0
- package/build/client/_app/immutable/chunks/BxFShcQO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ByzqAuXw.js +0 -3
- package/build/client/_app/immutable/chunks/ByzqAuXw.js.br +0 -0
- package/build/client/_app/immutable/chunks/ByzqAuXw.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C5VOyQCG.js.br +0 -0
- package/build/client/_app/immutable/chunks/C5VOyQCG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C_9BZILB.js.br +0 -0
- package/build/client/_app/immutable/chunks/C_9BZILB.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cg3dlX05.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cg3dlX05.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CjfxuHdN.js.br +0 -0
- package/build/client/_app/immutable/chunks/CjfxuHdN.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DYuMZGL5.js.br +0 -0
- package/build/client/_app/immutable/chunks/DYuMZGL5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DZQMsHM5.js.br +0 -0
- package/build/client/_app/immutable/chunks/DZQMsHM5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DZvnhU_8.js.br +0 -0
- package/build/client/_app/immutable/chunks/DZvnhU_8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Pw0jDB7M.js +0 -1
- package/build/client/_app/immutable/chunks/Pw0jDB7M.js.br +0 -0
- package/build/client/_app/immutable/chunks/Pw0jDB7M.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CNaTe-zm.js +0 -2
- package/build/client/_app/immutable/entry/app.CNaTe-zm.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CNaTe-zm.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.hxYnjcDu.js +0 -1
- package/build/client/_app/immutable/entry/start.hxYnjcDu.js.br +0 -0
- package/build/client/_app/immutable/entry/start.hxYnjcDu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.C3ELOf4c.js +0 -7
- package/build/client/_app/immutable/nodes/0.C3ELOf4c.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.C3ELOf4c.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.Fqso94b3.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Fqso94b3.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.BusCVJWk.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BusCVJWk.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DUlpocIc.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DUlpocIc.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.BSVqdrrD.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.BSVqdrrD.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.Cfj35gpY.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.Cfj35gpY.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.CG4eKRH0.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.CG4eKRH0.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.DHilxD1o.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.DHilxD1o.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.BjKgvSie.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.BjKgvSie.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.BRT6HOXB.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.BRT6HOXB.js.gz +0 -0
- package/build/server/chunks/1-Bw5KlAjL.js +0 -9
- package/build/server/chunks/6-BZ0enR6b.js +0 -9
- package/build/server/chunks/7-Lg8imTZn.js +0 -9
- package/build/server/chunks/8-DKs4yOL7.js +0 -9
- package/build/server/chunks/8-DKs4yOL7.js.map +0 -1
- package/build/server/chunks/9-UNmpUWDY.js +0 -9
- package/build/server/chunks/9-UNmpUWDY.js.map +0 -1
- package/build/server/chunks/Button-B5dU-ntz.js.map +0 -1
- package/build/server/chunks/Icon-C7Ml3GX6.js.map +0 -1
- package/build/server/chunks/Input-CPGO0sbS.js.map +0 -1
- package/build/server/chunks/Pill-CcrtCejm.js.map +0 -1
- package/build/server/chunks/Shimmer-C5jkvGr1.js.map +0 -1
- package/build/server/chunks/_layout.svelte-noB4j-v2.js.map +0 -1
- package/build/server/chunks/_page.svelte-C60lAagP.js.map +0 -1
- package/build/server/chunks/_page.svelte-DnTpPnPR.js.map +0 -1
- package/build/server/chunks/_server.ts-5wx4ZppI.js.map +0 -1
- package/build/server/chunks/_server.ts-B1z0q6qZ.js.map +0 -1
- package/build/server/chunks/_server.ts-Bt7EAfjo.js.map +0 -1
- package/build/server/chunks/_server.ts-CKXVBbwb.js.map +0 -1
- package/build/server/chunks/_server.ts-CgHc1Zpx.js.map +0 -1
- package/build/server/chunks/_server.ts-CyjDrcZN.js.map +0 -1
- package/build/server/chunks/_server.ts-DZ5naqSL.js.map +0 -1
- package/build/server/chunks/client2-DDP30_vY.js +0 -7
- package/build/server/chunks/opencode-db-path-BwaPufWf.js +0 -411
- package/build/server/chunks/opencode-db-path-BwaPufWf.js.map +0 -1
- package/build/server/chunks/pty-manager-RmhVe2Ez.js.map +0 -1
- package/build/server/chunks/qwen-reader-2fTFuC_D.js +0 -622
- package/build/server/chunks/qwen-reader-2fTFuC_D.js.map +0 -1
- package/build/server/chunks/registry-DzJj2E6I.js.map +0 -1
- package/build/server/chunks/root-xvQIR1Bt.js.map +0 -1
- package/build/server/chunks/stores-C-LqoonT.js.map +0 -1
- /package/build/client/_app/immutable/assets/{8.BhoBXADL.css → 10.BhoBXADL.css} +0 -0
- /package/build/client/_app/immutable/assets/{8.BhoBXADL.css.br → 10.BhoBXADL.css.br} +0 -0
- /package/build/client/_app/immutable/assets/{8.BhoBXADL.css.gz → 10.BhoBXADL.css.gz} +0 -0
- /package/build/client/_app/immutable/assets/{9.v5KA95xm.css → 11.v5KA95xm.css} +0 -0
- /package/build/client/_app/immutable/assets/{9.v5KA95xm.css.br → 11.v5KA95xm.css.br} +0 -0
- /package/build/client/_app/immutable/assets/{9.v5KA95xm.css.gz → 11.v5KA95xm.css.gz} +0 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SuperSessionCoordinator — the Session-Over-Sessions meta-layer.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to N running agent sessions (any provider) by reusing the very
|
|
5
|
+
* same session-watcher adapter the WS session handler uses, so it inherits
|
|
6
|
+
* support for all eight providers with no new watching code. It merges each
|
|
7
|
+
* member's messages into one source-tagged transcript and fans live updates
|
|
8
|
+
* out to WebSocket listeners. Phase 1: observe + merge + human-driven
|
|
9
|
+
* relay-forward. Phase 2 (this file): static routing rules auto-relay messages
|
|
10
|
+
* between members (relay/block/escalate) and a human approve/deny path for
|
|
11
|
+
* escalations.
|
|
12
|
+
*
|
|
13
|
+
* Loop guards: an injected (relayed) entry is never itself re-evaluated;
|
|
14
|
+
* self-relay rules are rejected; and a per-pair cooldown rate-limits a chatty
|
|
15
|
+
* rule. NOTE these stop self-cascade and throttle but do NOT stop a bidirectional
|
|
16
|
+
* ping-pong: with both A→B and B→A rules, the target's genuine response arrives
|
|
17
|
+
* via the watcher (relayed=false) and is evaluated, so the pair can exchange one
|
|
18
|
+
* volley per cooldown indefinitely. Full cycle detection is future work.
|
|
19
|
+
*
|
|
20
|
+
* Dependencies (watcher + injector) are injected from server.ts so this module
|
|
21
|
+
* has no direct PTY/watcher imports and is trivially testable with fakes.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
SessionSource,
|
|
26
|
+
SessionWatcherLike,
|
|
27
|
+
SosInjector,
|
|
28
|
+
SosListener,
|
|
29
|
+
SosMember,
|
|
30
|
+
SosRoutingRule,
|
|
31
|
+
SosServerMessage,
|
|
32
|
+
SosTranscriptEntry,
|
|
33
|
+
SuperSession,
|
|
34
|
+
SuperSessionRuntime,
|
|
35
|
+
} from '$lib/types';
|
|
36
|
+
|
|
37
|
+
import { randomBytes } from 'crypto';
|
|
38
|
+
|
|
39
|
+
import { evaluateRelayRule, isValidRoutingRule } from './policy-gate';
|
|
40
|
+
import { relayStore } from './relay-store';
|
|
41
|
+
|
|
42
|
+
const MAX_TRANSCRIPT = 500;
|
|
43
|
+
const MAX_LABEL_LEN = 200;
|
|
44
|
+
const RELAY_COOLDOWN_MS = 5000;
|
|
45
|
+
const ESCALATE_TIMEOUT_MS = 120_000;
|
|
46
|
+
const MAX_RELAY_TEXT = 10240; // 10 KB — same cap manual forward enforces at the route/WS layer
|
|
47
|
+
|
|
48
|
+
class SuperSessionCoordinator {
|
|
49
|
+
private injector: null | SosInjector = null;
|
|
50
|
+
private runtimes = new Map<string, SuperSessionRuntime>();
|
|
51
|
+
private watcher: null | SessionWatcherLike = null;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Add a member to a super-session: persist it, replay its history into the
|
|
55
|
+
* merged transcript, and subscribe for live updates. Returns the new member,
|
|
56
|
+
* or null if the super-session does not exist.
|
|
57
|
+
*/
|
|
58
|
+
addMember(
|
|
59
|
+
superSessionId: string,
|
|
60
|
+
spec: {
|
|
61
|
+
capability?: string;
|
|
62
|
+
provider: SessionSource;
|
|
63
|
+
sessionKey: string;
|
|
64
|
+
terminalId?: null | string;
|
|
65
|
+
}
|
|
66
|
+
): null | SosMember {
|
|
67
|
+
const rt = this.runtimes.get(superSessionId);
|
|
68
|
+
if (!rt) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const member: SosMember = {
|
|
72
|
+
capability: spec.capability ?? '',
|
|
73
|
+
id: randomBytes(8).toString('hex'),
|
|
74
|
+
provider: spec.provider,
|
|
75
|
+
registeredAt: new Date().toISOString(),
|
|
76
|
+
sessionKey: spec.sessionKey,
|
|
77
|
+
status: 'Idle',
|
|
78
|
+
terminalId: spec.terminalId ?? null,
|
|
79
|
+
};
|
|
80
|
+
rt.session.members.push(member);
|
|
81
|
+
relayStore.addMember(superSessionId, member);
|
|
82
|
+
this.subscribeMember(rt, member);
|
|
83
|
+
this.emit(rt, { member, type: 'sos-member-added' });
|
|
84
|
+
return member;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Human approves an escalated relay: inject it now, then resolve. */
|
|
88
|
+
approveRelay(superSessionId: string, relayId: string): boolean {
|
|
89
|
+
const rt = this.runtimes.get(superSessionId);
|
|
90
|
+
const pending = rt?.pending.get(relayId);
|
|
91
|
+
if (!rt || !pending) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const result = this.doRelay(rt, pending.toMemberId, pending.text);
|
|
95
|
+
if (!result.ok) {
|
|
96
|
+
this.emit(rt, { message: result.error ?? 'Injection failed', type: 'sos-error' });
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
this.resolvePending(rt, relayId, 'approved');
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
createSuperSession(label: string): SuperSession {
|
|
104
|
+
const session: SuperSession = {
|
|
105
|
+
createdAt: new Date().toISOString(),
|
|
106
|
+
id: randomBytes(8).toString('hex'),
|
|
107
|
+
label: label.slice(0, MAX_LABEL_LEN),
|
|
108
|
+
members: [],
|
|
109
|
+
routingRules: [],
|
|
110
|
+
status: 'active',
|
|
111
|
+
transcript: [],
|
|
112
|
+
};
|
|
113
|
+
relayStore.createSuperSession(session);
|
|
114
|
+
this.runtimes.set(session.id, {
|
|
115
|
+
cooldowns: new Map(),
|
|
116
|
+
listeners: new Set(),
|
|
117
|
+
pending: new Map(),
|
|
118
|
+
session,
|
|
119
|
+
unsubscribes: new Map(),
|
|
120
|
+
});
|
|
121
|
+
return session;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
deleteSuperSession(id: string): boolean {
|
|
125
|
+
const rt = this.runtimes.get(id);
|
|
126
|
+
if (!rt) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
for (const unsub of rt.unsubscribes.values()) {
|
|
130
|
+
unsub();
|
|
131
|
+
}
|
|
132
|
+
rt.unsubscribes.clear();
|
|
133
|
+
for (const p of rt.pending.values()) {
|
|
134
|
+
if (p.timer) {
|
|
135
|
+
clearTimeout(p.timer);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
rt.pending.clear();
|
|
139
|
+
relayStore.deleteSuperSession(id);
|
|
140
|
+
this.runtimes.delete(id);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Human denies an escalated relay: drop it. */
|
|
145
|
+
denyRelay(superSessionId: string, relayId: string): boolean {
|
|
146
|
+
const rt = this.runtimes.get(superSessionId);
|
|
147
|
+
if (!rt?.pending.has(relayId)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
this.resolvePending(rt, relayId, 'denied');
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getRoutingRules(superSessionId: string): null | SosRoutingRule[] {
|
|
155
|
+
return this.runtimes.get(superSessionId)?.session.routingRules ?? null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getSuperSession(id: string): SuperSession | undefined {
|
|
159
|
+
return this.runtimes.get(id)?.session;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
listSuperSessions(): SuperSession[] {
|
|
163
|
+
return [...this.runtimes.values()].map((rt) => rt.session);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Rebuild persisted super-sessions and re-subscribe members. Call once on boot. */
|
|
167
|
+
reconnectAll(): void {
|
|
168
|
+
// Idempotent: tear down any existing runtimes (subscriptions + timers) so a
|
|
169
|
+
// second call cannot double-subscribe watchers or leak pending timers.
|
|
170
|
+
for (const rt of this.runtimes.values()) {
|
|
171
|
+
for (const unsub of rt.unsubscribes.values()) {
|
|
172
|
+
unsub();
|
|
173
|
+
}
|
|
174
|
+
for (const p of rt.pending.values()) {
|
|
175
|
+
if (p.timer) {
|
|
176
|
+
clearTimeout(p.timer);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.runtimes.clear();
|
|
181
|
+
|
|
182
|
+
for (const session of relayStore.loadAll()) {
|
|
183
|
+
const rt: SuperSessionRuntime = {
|
|
184
|
+
cooldowns: new Map(),
|
|
185
|
+
listeners: new Set(),
|
|
186
|
+
pending: new Map(),
|
|
187
|
+
session: { ...session, transcript: [] },
|
|
188
|
+
unsubscribes: new Map(),
|
|
189
|
+
};
|
|
190
|
+
this.runtimes.set(session.id, rt);
|
|
191
|
+
for (const member of rt.session.members) {
|
|
192
|
+
this.subscribeMember(rt, member);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Human-driven relay: inject text into a member's Shooter-owned terminal and
|
|
199
|
+
* record it as a relayed entry in the transcript. Returns an error string on
|
|
200
|
+
* failure, or null on success.
|
|
201
|
+
*/
|
|
202
|
+
relayForward(superSessionId: string, toMemberId: string, text: string): null | string {
|
|
203
|
+
const rt = this.runtimes.get(superSessionId);
|
|
204
|
+
if (!rt) {
|
|
205
|
+
return 'Super-session not found';
|
|
206
|
+
}
|
|
207
|
+
const member = rt.session.members.find((m) => m.id === toMemberId);
|
|
208
|
+
if (!member) {
|
|
209
|
+
return 'Target member not found';
|
|
210
|
+
}
|
|
211
|
+
if (!member.terminalId) {
|
|
212
|
+
return 'Target member has no Shooter-owned terminal (cannot inject into an observed session)';
|
|
213
|
+
}
|
|
214
|
+
if (!this.injector) {
|
|
215
|
+
return 'Injector not configured';
|
|
216
|
+
}
|
|
217
|
+
const result = this.doRelay(rt, toMemberId, text);
|
|
218
|
+
return result.ok ? null : (result.error ?? 'Injection failed');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
removeMember(superSessionId: string, memberId: string): boolean {
|
|
222
|
+
const rt = this.runtimes.get(superSessionId);
|
|
223
|
+
if (!rt) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const idx = rt.session.members.findIndex((m) => m.id === memberId);
|
|
227
|
+
if (idx === -1) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
rt.unsubscribes.get(memberId)?.();
|
|
231
|
+
rt.unsubscribes.delete(memberId);
|
|
232
|
+
rt.session.members.splice(idx, 1);
|
|
233
|
+
relayStore.removeMember(memberId);
|
|
234
|
+
this.emit(rt, { memberId, type: 'sos-member-removed' });
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Inject the relay function (server.ts owns PtyManager). */
|
|
239
|
+
setInjector(injector: SosInjector): void {
|
|
240
|
+
this.injector = injector;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Replace a super-session's routing rules (Phase 2). Rejects self-relay rules.
|
|
245
|
+
* Returns an error string on invalid input, or null on success.
|
|
246
|
+
*/
|
|
247
|
+
setRoutingRules(superSessionId: string, rules: SosRoutingRule[]): null | string {
|
|
248
|
+
const rt = this.runtimes.get(superSessionId);
|
|
249
|
+
if (!rt) {
|
|
250
|
+
return 'Super-session not found';
|
|
251
|
+
}
|
|
252
|
+
for (const rule of rules) {
|
|
253
|
+
if (!isValidRoutingRule(rule)) {
|
|
254
|
+
return `Invalid rule ${rule.id}: a rule may not relay a member to itself or to ANY`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
rt.session.routingRules = rules;
|
|
258
|
+
relayStore.createSuperSession(rt.session); // INSERT OR REPLACE rewrites routing_rules
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Update a super-session's lifecycle status. Returns false if unknown id. */
|
|
263
|
+
setStatus(id: string, status: SuperSession['status']): boolean {
|
|
264
|
+
const rt = this.runtimes.get(id);
|
|
265
|
+
if (!rt) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
rt.session.status = status;
|
|
269
|
+
relayStore.updateSuperSessionStatus(id, status);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Inject the session-watcher adapter (same one the WS session handler uses). */
|
|
274
|
+
setWatcher(watcher: SessionWatcherLike): void {
|
|
275
|
+
this.watcher = watcher;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Register a WS listener for a super-session and replay the current merged
|
|
280
|
+
* transcript to it. Returns an unsubscribe function, or null if unknown id.
|
|
281
|
+
*/
|
|
282
|
+
subscribe(superSessionId: string, listener: SosListener): (() => void) | null {
|
|
283
|
+
const rt = this.runtimes.get(superSessionId);
|
|
284
|
+
if (!rt) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
rt.listeners.add(listener);
|
|
288
|
+
listener({ entries: [...rt.session.transcript], type: 'sos-history' });
|
|
289
|
+
return () => {
|
|
290
|
+
rt.listeners.delete(listener);
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private appendEntry(rt: SuperSessionRuntime, entry: SosTranscriptEntry): void {
|
|
295
|
+
rt.session.transcript.push(entry);
|
|
296
|
+
if (rt.session.transcript.length > MAX_TRANSCRIPT) {
|
|
297
|
+
rt.session.transcript.splice(0, rt.session.transcript.length - MAX_TRANSCRIPT);
|
|
298
|
+
}
|
|
299
|
+
this.emit(rt, { entry, type: 'sos-message' });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Run Tier-1 routing for one new message and dispatch the matched action. */
|
|
303
|
+
private applyPolicy(rt: SuperSessionRuntime, fromMemberId: string, text: string): void {
|
|
304
|
+
if (!text) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const rule = evaluateRelayRule(rt.session.routingRules, fromMemberId, text);
|
|
308
|
+
if (!rule) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (rule.action === 'relay') {
|
|
312
|
+
this.autoRelay(rt, fromMemberId, rule.toMemberId, text);
|
|
313
|
+
} else if (rule.action === 'escalate') {
|
|
314
|
+
this.escalate(rt, fromMemberId, rule.toMemberId, text);
|
|
315
|
+
}
|
|
316
|
+
// 'block' → intentionally do nothing (the message merges but is not relayed).
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Rule-driven relay, suppressed by the per-pair cooldown. */
|
|
320
|
+
private autoRelay(
|
|
321
|
+
rt: SuperSessionRuntime,
|
|
322
|
+
fromMemberId: string,
|
|
323
|
+
toMemberId: string,
|
|
324
|
+
text: string
|
|
325
|
+
): void {
|
|
326
|
+
const key = `${fromMemberId}:${toMemberId}`;
|
|
327
|
+
const now = Date.now();
|
|
328
|
+
if (now - (rt.cooldowns.get(key) ?? 0) < RELAY_COOLDOWN_MS) {
|
|
329
|
+
return; // cooldown active — bound the blast radius of a chatty rule
|
|
330
|
+
}
|
|
331
|
+
rt.cooldowns.set(key, now);
|
|
332
|
+
this.doRelay(rt, toMemberId, text);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Inject text into a member's terminal and record a relayed transcript entry
|
|
337
|
+
* (relayed=true is the primary loop guard). Shared by manual + auto relay.
|
|
338
|
+
*/
|
|
339
|
+
private doRelay(
|
|
340
|
+
rt: SuperSessionRuntime,
|
|
341
|
+
toMemberId: string,
|
|
342
|
+
text: string
|
|
343
|
+
): { error?: string; ok: boolean } {
|
|
344
|
+
const member = rt.session.members.find((m) => m.id === toMemberId);
|
|
345
|
+
if (!member) {
|
|
346
|
+
return { error: 'Target member not found', ok: false };
|
|
347
|
+
}
|
|
348
|
+
if (!member.terminalId) {
|
|
349
|
+
return {
|
|
350
|
+
error:
|
|
351
|
+
'Target member has no Shooter-owned terminal (cannot inject into an observed session)',
|
|
352
|
+
ok: false,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (!this.injector) {
|
|
356
|
+
return { error: 'Injector not configured', ok: false };
|
|
357
|
+
}
|
|
358
|
+
// Cap injected text so auto-relay can't dump an oversized agent message into
|
|
359
|
+
// a PTY — matches the 10 KB limit manual forward enforces at the boundary.
|
|
360
|
+
const capped = text.length > MAX_RELAY_TEXT ? text.slice(0, MAX_RELAY_TEXT) : text;
|
|
361
|
+
const result = this.injector(member.terminalId, capped);
|
|
362
|
+
if (!result.ok) {
|
|
363
|
+
return { error: result.error ?? 'Injection failed', ok: false };
|
|
364
|
+
}
|
|
365
|
+
this.appendEntry(rt, {
|
|
366
|
+
memberId: toMemberId,
|
|
367
|
+
message: {
|
|
368
|
+
id: `relay-${randomBytes(6).toString('hex')}`,
|
|
369
|
+
parts: [{ content: capped, type: 'text' }],
|
|
370
|
+
role: 'user',
|
|
371
|
+
timestamp: new Date().toISOString(),
|
|
372
|
+
},
|
|
373
|
+
provider: member.provider,
|
|
374
|
+
relayed: true,
|
|
375
|
+
});
|
|
376
|
+
return { ok: true };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private emit(rt: SuperSessionRuntime, msg: SosServerMessage): void {
|
|
380
|
+
for (const listener of rt.listeners) {
|
|
381
|
+
try {
|
|
382
|
+
listener(msg);
|
|
383
|
+
} catch (err) {
|
|
384
|
+
console.error('[sos] listener error:', err);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** Queue an escalated relay for human approval and notify listeners. */
|
|
390
|
+
private escalate(
|
|
391
|
+
rt: SuperSessionRuntime,
|
|
392
|
+
fromMemberId: string,
|
|
393
|
+
toMemberId: string,
|
|
394
|
+
text: string
|
|
395
|
+
): void {
|
|
396
|
+
const relayId = randomBytes(8).toString('hex');
|
|
397
|
+
const expiresAt = new Date(Date.now() + ESCALATE_TIMEOUT_MS).toISOString();
|
|
398
|
+
const timer = setTimeout(() => {
|
|
399
|
+
this.resolvePending(rt, relayId, 'expired');
|
|
400
|
+
}, ESCALATE_TIMEOUT_MS);
|
|
401
|
+
if (typeof timer.unref === 'function') {
|
|
402
|
+
timer.unref();
|
|
403
|
+
}
|
|
404
|
+
rt.pending.set(relayId, {
|
|
405
|
+
createdAt: new Date().toISOString(),
|
|
406
|
+
expiresAt,
|
|
407
|
+
fromMemberId,
|
|
408
|
+
id: relayId,
|
|
409
|
+
text,
|
|
410
|
+
timer,
|
|
411
|
+
toMemberId,
|
|
412
|
+
});
|
|
413
|
+
this.emit(rt, {
|
|
414
|
+
expiresAt,
|
|
415
|
+
fromMemberId,
|
|
416
|
+
preview: text.slice(0, 200),
|
|
417
|
+
relayId,
|
|
418
|
+
toMemberId,
|
|
419
|
+
type: 'sos-relay-pending',
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private resolvePending(
|
|
424
|
+
rt: SuperSessionRuntime,
|
|
425
|
+
relayId: string,
|
|
426
|
+
decision: 'approved' | 'denied' | 'expired'
|
|
427
|
+
): void {
|
|
428
|
+
const pending = rt.pending.get(relayId);
|
|
429
|
+
if (!pending) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (pending.timer) {
|
|
433
|
+
clearTimeout(pending.timer);
|
|
434
|
+
}
|
|
435
|
+
rt.pending.delete(relayId);
|
|
436
|
+
this.emit(rt, { decision, relayId, type: 'sos-relay-resolved' });
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Replay history + subscribe a single member to the merged transcript. */
|
|
440
|
+
private subscribeMember(rt: SuperSessionRuntime, member: SosMember): void {
|
|
441
|
+
if (!this.watcher) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
// Replay current history as tagged entries.
|
|
445
|
+
try {
|
|
446
|
+
for (const message of this.watcher.getHistory(member.sessionKey)) {
|
|
447
|
+
this.appendEntry(rt, {
|
|
448
|
+
memberId: member.id,
|
|
449
|
+
message,
|
|
450
|
+
provider: member.provider,
|
|
451
|
+
relayed: false,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
} catch (err) {
|
|
455
|
+
console.error(`[sos] history replay failed for ${member.sessionKey}:`, err);
|
|
456
|
+
}
|
|
457
|
+
// Subscribe for live updates (the watcher emits only post-subscribe messages).
|
|
458
|
+
try {
|
|
459
|
+
const unsub = this.watcher.subscribe(member.sessionKey, (messages) => {
|
|
460
|
+
for (const message of messages) {
|
|
461
|
+
this.appendEntry(rt, {
|
|
462
|
+
memberId: member.id,
|
|
463
|
+
message,
|
|
464
|
+
provider: member.provider,
|
|
465
|
+
relayed: false,
|
|
466
|
+
});
|
|
467
|
+
// Phase 2: evaluate routing rules against genuinely-new member output.
|
|
468
|
+
this.applyPolicy(rt, member.id, textOf(message));
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
rt.unsubscribes.set(member.id, unsub);
|
|
472
|
+
} catch (err) {
|
|
473
|
+
console.error(`[sos] subscribe failed for ${member.sessionKey}:`, err);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Concatenate the text parts of a message (the basis for rule matching). */
|
|
479
|
+
function textOf(message: SosTranscriptEntry['message']): string {
|
|
480
|
+
return message.parts
|
|
481
|
+
.filter((p) => p.type === 'text')
|
|
482
|
+
.map((p) => (p.type === 'text' ? p.content : ''))
|
|
483
|
+
.join(' ')
|
|
484
|
+
.trim();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Single shared instance across module loaders.
|
|
488
|
+
const SC_GLOBAL_KEY = '__shooter_sos_coordinator';
|
|
489
|
+
export const sosCoordinator: SuperSessionCoordinator =
|
|
490
|
+
((globalThis as Record<string, unknown>)[SC_GLOBAL_KEY] as SuperSessionCoordinator) ||
|
|
491
|
+
new SuperSessionCoordinator();
|
|
492
|
+
(globalThis as Record<string, unknown>)[SC_GLOBAL_KEY] = sosCoordinator;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier-1 static routing policy for the SoS coordinator (Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* Pure, side-effect-free evaluation: given a super-session's routing rules and a
|
|
5
|
+
* new message from a member, return the first matching rule (lowest `priority`
|
|
6
|
+
* wins) or null. The coordinator decides what to do with the result
|
|
7
|
+
* (relay / block / escalate). The Tier-2 LLM judge from the design is a later
|
|
8
|
+
* phase and is intentionally absent here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SosRoutingRule } from '$lib/types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* First matching rule for a message, or null when none match (default: no
|
|
15
|
+
* relay — rules are opt-in). Rules are evaluated in ascending `priority`.
|
|
16
|
+
*/
|
|
17
|
+
export function evaluateRelayRule(
|
|
18
|
+
rules: SosRoutingRule[],
|
|
19
|
+
fromMemberId: string,
|
|
20
|
+
text: string
|
|
21
|
+
): null | SosRoutingRule {
|
|
22
|
+
const ordered = [...rules].sort((a, b) => a.priority - b.priority);
|
|
23
|
+
for (const rule of ordered) {
|
|
24
|
+
if (rule.fromMemberId !== 'ANY' && rule.fromMemberId !== fromMemberId) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
// An ANY-source rule must never relay a member's own message back to itself
|
|
28
|
+
// (explicit from === to rules are already rejected at rule-creation time).
|
|
29
|
+
if (rule.toMemberId === fromMemberId) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!matchesPattern(text, rule.matchPattern)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
return rule;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A rule is valid when it does not self-relay (from === to would loop) and it
|
|
42
|
+
* targets a concrete member (broadcast 'ANY' as a destination is not supported
|
|
43
|
+
* in Phase 2). ANY-source rules are additionally guarded at evaluation time so
|
|
44
|
+
* they never match a message originating from their own target.
|
|
45
|
+
*/
|
|
46
|
+
export function isValidRoutingRule(rule: SosRoutingRule): boolean {
|
|
47
|
+
return rule.toMemberId !== 'ANY' && rule.fromMemberId !== rule.toMemberId;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Empty pattern matches everything; otherwise a case-insensitive substring test. */
|
|
51
|
+
function matchesPattern(text: string, pattern: string): boolean {
|
|
52
|
+
if (!pattern) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return text.toLowerCase().includes(pattern.toLowerCase());
|
|
56
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelayStore — SQLite persistence for Session-Over-Sessions metadata.
|
|
3
|
+
*
|
|
4
|
+
* Persists super-sessions and their member rows to ~/.shooter/shooter.db so a
|
|
5
|
+
* super-session survives a server restart (the coordinator re-subscribes each
|
|
6
|
+
* member on boot). The merged transcript itself is an in-memory ring buffer and
|
|
7
|
+
* is intentionally NOT persisted — it is rebuilt from each member's history on
|
|
8
|
+
* reconnect. Uses its own WAL connection to the shared db, exactly like
|
|
9
|
+
* terminal-store.
|
|
10
|
+
*
|
|
11
|
+
* The Phase 2/3 tables (sos_relay_log, relay_decisions, sos_memory) from the
|
|
12
|
+
* design are not created here yet — this is the Phase 1 MVP.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { SosMember, SuperSession } from '$lib/types';
|
|
16
|
+
|
|
17
|
+
import Database from 'better-sqlite3';
|
|
18
|
+
import * as fs from 'fs';
|
|
19
|
+
import * as os from 'os';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
|
|
22
|
+
const DB_DIR = path.join(os.homedir(), '.shooter');
|
|
23
|
+
const DB_PATH = path.join(DB_DIR, 'shooter.db');
|
|
24
|
+
|
|
25
|
+
class RelayStore {
|
|
26
|
+
private db: Database.Database;
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
fs.mkdirSync(DB_DIR, { recursive: true });
|
|
30
|
+
this.db = new Database(DB_PATH);
|
|
31
|
+
this.db.pragma('journal_mode = WAL');
|
|
32
|
+
this.db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS super_sessions (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
label TEXT NOT NULL,
|
|
36
|
+
routing_rules TEXT NOT NULL DEFAULT '[]',
|
|
37
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
38
|
+
created_at TEXT NOT NULL
|
|
39
|
+
);
|
|
40
|
+
CREATE TABLE IF NOT EXISTS sos_sessions (
|
|
41
|
+
id TEXT PRIMARY KEY,
|
|
42
|
+
super_session_id TEXT NOT NULL,
|
|
43
|
+
session_key TEXT NOT NULL,
|
|
44
|
+
terminal_id TEXT,
|
|
45
|
+
provider TEXT NOT NULL,
|
|
46
|
+
capability TEXT NOT NULL DEFAULT '',
|
|
47
|
+
status TEXT NOT NULL DEFAULT 'Idle',
|
|
48
|
+
registered_at TEXT NOT NULL
|
|
49
|
+
);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_sos_sessions_super ON sos_sessions(super_session_id);
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
addMember(superSessionId: string, member: SosMember): void {
|
|
55
|
+
this.db
|
|
56
|
+
.prepare(
|
|
57
|
+
`INSERT OR REPLACE INTO sos_sessions
|
|
58
|
+
(id, super_session_id, session_key, terminal_id, provider, capability, status, registered_at)
|
|
59
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
60
|
+
)
|
|
61
|
+
.run(
|
|
62
|
+
member.id,
|
|
63
|
+
superSessionId,
|
|
64
|
+
member.sessionKey,
|
|
65
|
+
member.terminalId,
|
|
66
|
+
member.provider,
|
|
67
|
+
member.capability,
|
|
68
|
+
member.status,
|
|
69
|
+
member.registeredAt
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createSuperSession(session: SuperSession): void {
|
|
74
|
+
this.db
|
|
75
|
+
.prepare(
|
|
76
|
+
`INSERT OR REPLACE INTO super_sessions (id, label, routing_rules, status, created_at)
|
|
77
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
78
|
+
)
|
|
79
|
+
.run(
|
|
80
|
+
session.id,
|
|
81
|
+
session.label,
|
|
82
|
+
JSON.stringify(session.routingRules),
|
|
83
|
+
session.status,
|
|
84
|
+
session.createdAt
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
deleteSuperSession(id: string): void {
|
|
89
|
+
// Atomic: both dependent deletes succeed or neither does, so an interruption
|
|
90
|
+
// cannot leave orphaned member rows behind their parent super-session.
|
|
91
|
+
const tx = this.db.transaction((sid: string) => {
|
|
92
|
+
this.db.prepare('DELETE FROM sos_sessions WHERE super_session_id = ?').run(sid);
|
|
93
|
+
this.db.prepare('DELETE FROM super_sessions WHERE id = ?').run(sid);
|
|
94
|
+
});
|
|
95
|
+
tx(id);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Rebuild every persisted super-session (members included, transcript empty). */
|
|
99
|
+
loadAll(): SuperSession[] {
|
|
100
|
+
const rows = this.db.prepare('SELECT * FROM super_sessions').all() as Record<string, unknown>[];
|
|
101
|
+
return rows.map((row) => {
|
|
102
|
+
const id = row.id as string;
|
|
103
|
+
const memberRows = this.db
|
|
104
|
+
.prepare('SELECT * FROM sos_sessions WHERE super_session_id = ? ORDER BY registered_at')
|
|
105
|
+
.all(id) as Record<string, unknown>[];
|
|
106
|
+
return {
|
|
107
|
+
createdAt: row.created_at as string,
|
|
108
|
+
id,
|
|
109
|
+
label: row.label as string,
|
|
110
|
+
members: memberRows.map(rowToMember),
|
|
111
|
+
routingRules: safeParseRules(row.routing_rules),
|
|
112
|
+
status: row.status as SuperSession['status'],
|
|
113
|
+
transcript: [],
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
removeMember(memberId: string): void {
|
|
119
|
+
this.db.prepare('DELETE FROM sos_sessions WHERE id = ?').run(memberId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
updateMemberStatus(memberId: string, status: SosMember['status']): void {
|
|
123
|
+
this.db.prepare('UPDATE sos_sessions SET status = ? WHERE id = ?').run(status, memberId);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
updateSuperSessionStatus(id: string, status: SuperSession['status']): void {
|
|
127
|
+
this.db.prepare('UPDATE super_sessions SET status = ? WHERE id = ?').run(status, id);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function rowToMember(row: Record<string, unknown>): SosMember {
|
|
132
|
+
return {
|
|
133
|
+
capability: (row.capability as string) ?? '',
|
|
134
|
+
id: row.id as string,
|
|
135
|
+
provider: row.provider as SosMember['provider'],
|
|
136
|
+
registeredAt: row.registered_at as string,
|
|
137
|
+
sessionKey: row.session_key as string,
|
|
138
|
+
status: row.status as SosMember['status'],
|
|
139
|
+
terminalId: (row.terminal_id as string) ?? null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function safeParseRules(raw: unknown): SuperSession['routingRules'] {
|
|
144
|
+
if (typeof raw !== 'string') {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const parsed: unknown = JSON.parse(raw);
|
|
149
|
+
return Array.isArray(parsed) ? (parsed as SuperSession['routingRules']) : [];
|
|
150
|
+
} catch {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Single shared instance across module loaders.
|
|
156
|
+
const RS_GLOBAL_KEY = '__shooter_relay_store';
|
|
157
|
+
export const relayStore: RelayStore =
|
|
158
|
+
((globalThis as Record<string, unknown>)[RS_GLOBAL_KEY] as RelayStore) || new RelayStore();
|
|
159
|
+
(globalThis as Record<string, unknown>)[RS_GLOBAL_KEY] = relayStore;
|