@juspay/shooter 1.20.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/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/{DT4H19pV.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/{C_YNQL8b.js → DOEXXmsh.js} +2 -2
- 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/{DIZ3Qst5.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/{BB2l8o4X.js → i5iZvmIH.js} +1 -1
- package/build/client/_app/immutable/chunks/i5iZvmIH.js.br +0 -0
- package/build/client/_app/immutable/chunks/i5iZvmIH.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.Bd-DfeJi.js → app.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.DT4dq6Ay.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.CF7RGXpe.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.BV_G7yLI.js → 11.CKmZjP_a.js} +1 -1
- 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.0MMe3oxR.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.ComiWlV6.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.vkPx1kVP.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.Bmr3sWbS.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.CAJucyeI.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-DDGB6CRT.js → 0-vrTNAfZB.js} +4 -2
- package/build/server/chunks/0-vrTNAfZB.js.map +1 -0
- package/build/server/chunks/{1-DEjonQXD.js → 1-nbr-bOoF.js} +2 -2
- package/build/server/chunks/{1-DEjonQXD.js.map → 1-nbr-bOoF.js.map} +1 -1
- package/build/server/chunks/{10-BK1kiiiw.js → 10-ChyvvJ6w.js} +2 -2
- package/build/server/chunks/{10-BK1kiiiw.js.map → 10-ChyvvJ6w.js.map} +1 -1
- package/build/server/chunks/{11-CJPjkEF3.js → 11-6ZAjL3uU.js} +2 -2
- package/build/server/chunks/{11-CJPjkEF3.js.map → 11-6ZAjL3uU.js.map} +1 -1
- package/build/server/chunks/{2-RLnhlWh5.js → 2-DWFRVDWJ.js} +3 -3
- package/build/server/chunks/{2-RLnhlWh5.js.map → 2-DWFRVDWJ.js.map} +1 -1
- package/build/server/chunks/{3-Dd4pJBqZ.js → 3-CKANM_WM.js} +2 -2
- package/build/server/chunks/{3-Dd4pJBqZ.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-DdRMnKNa.js → 6-Cbf1AAMQ.js} +2 -2
- package/build/server/chunks/{6-DdRMnKNa.js.map → 6-Cbf1AAMQ.js.map} +1 -1
- package/build/server/chunks/{7-vLOMMetm.js → 7-CMK2quEf.js} +2 -2
- package/build/server/chunks/{7-vLOMMetm.js.map → 7-CMK2quEf.js.map} +1 -1
- package/build/server/chunks/{8-rJyiQLFs.js → 8-DhdfkfDM.js} +2 -2
- package/build/server/chunks/{8-rJyiQLFs.js.map → 8-DhdfkfDM.js.map} +1 -1
- package/build/server/chunks/{9-CVSNNYED.js → 9-CPpxtRM5.js} +2 -2
- package/build/server/chunks/{9-CVSNNYED.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-C7B0qdrC.js.map +1 -1
- package/build/server/chunks/{_page.svelte-BLo2v_8E.js → _page.svelte-Gv9p8nlS.js} +3 -4
- package/build/server/chunks/_page.svelte-Gv9p8nlS.js.map +1 -0
- package/build/server/chunks/_page.svelte-dabsQl9c.js.map +1 -1
- package/build/server/chunks/_page.svelte-tBuIq8Pg.js.map +1 -1
- 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-bk_EeAdY.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-B2wIgsW4.js → _server.ts-CA5KUENM.js} +3 -3
- package/build/server/chunks/{_server.ts-B2wIgsW4.js.map → _server.ts-CA5KUENM.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DEx9-epI.js → _server.ts-CC2K8-L2.js} +3 -3
- package/build/server/chunks/{_server.ts-DEx9-epI.js.map → _server.ts-CC2K8-L2.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CJGyN8mw.js → _server.ts-CD7JP3fz.js} +3 -3
- package/build/server/chunks/{_server.ts-CJGyN8mw.js.map → _server.ts-CD7JP3fz.js.map} +1 -1
- package/build/server/chunks/{_server.ts-AnBXfZXh.js → _server.ts-D0zRDSx0.js} +2 -2
- package/build/server/chunks/{_server.ts-AnBXfZXh.js.map → _server.ts-D0zRDSx0.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DKNIsQeH.js → _server.ts-Dp-hXW_I.js} +3 -3
- package/build/server/chunks/{_server.ts-DKNIsQeH.js.map → _server.ts-Dp-hXW_I.js.map} +1 -1
- 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-Dz9Jd9Jh.js → _server.ts-X1R7L_QI.js} +3 -3
- package/build/server/chunks/{_server.ts-Dz9Jd9Jh.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-t0-7Zv5q.js → guest-registry-Dxvd7p-g.js} +10 -1
- package/build/server/chunks/guest-registry-Dxvd7p-g.js.map +1 -0
- package/build/server/chunks/index-CoYB03g7.js.map +1 -1
- package/build/server/chunks/index2-dSGQ9Eaa.js.map +1 -1
- package/build/server/chunks/{pty-manager-CkZNoW1t.js → pty-manager-ZqXqa-6A.js} +2 -2
- package/build/server/chunks/{pty-manager-CkZNoW1t.js.map → pty-manager-ZqXqa-6A.js.map} +1 -1
- package/build/server/chunks/root-D4IoFC8F.js.map +1 -1
- package/build/server/chunks/state.svelte-CmHqngc_.js.map +1 -1
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +35 -21
- package/build/server/manifest.js.map +1 -1
- package/package.json +2 -2
- package/server.ts +2 -0
- 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/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/ws/events-handler.ts +32 -0
- package/src/lib/types/autopilot.ts +73 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/types/terminal-client.ts +2 -0
- package/src/routes/+layout.server.ts +2 -0
- package/src/routes/+layout.svelte +19 -4
- package/src/routes/+page.svelte +9 -1
- package/src/routes/api/autopilot/+server.ts +74 -0
- package/src/routes/api/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/build/client/_app/immutable/assets/2.DjiwkLqE.css +0 -1
- package/build/client/_app/immutable/assets/2.DjiwkLqE.css.br +0 -0
- package/build/client/_app/immutable/assets/2.DjiwkLqE.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BB2l8o4X.js.br +0 -0
- package/build/client/_app/immutable/chunks/BB2l8o4X.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BvmdJful.js.br +0 -0
- package/build/client/_app/immutable/chunks/BvmdJful.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C_YNQL8b.js.br +0 -0
- package/build/client/_app/immutable/chunks/C_YNQL8b.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ClIPTXf3.js.br +0 -0
- package/build/client/_app/immutable/chunks/ClIPTXf3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DIZ3Qst5.js.br +0 -0
- package/build/client/_app/immutable/chunks/DIZ3Qst5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DT4H19pV.js.br +0 -0
- package/build/client/_app/immutable/chunks/DT4H19pV.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ZS5XYDx_.js.br +0 -0
- package/build/client/_app/immutable/chunks/pRcLbE0d.js +0 -1
- package/build/client/_app/immutable/chunks/pRcLbE0d.js.br +0 -0
- package/build/client/_app/immutable/chunks/pRcLbE0d.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.evvp4tX7.js +0 -1
- package/build/client/_app/immutable/entry/start.evvp4tX7.js.br +0 -2
- package/build/client/_app/immutable/entry/start.evvp4tX7.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js +0 -10
- package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DcRhsjYp.js +0 -13
- package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.ComiWlV6.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.ComiWlV6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.CAJucyeI.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.CAJucyeI.js.gz +0 -0
- package/build/server/chunks/0-DDGB6CRT.js.map +0 -1
- package/build/server/chunks/_page.svelte-BLo2v_8E.js.map +0 -1
- package/build/server/chunks/_server.ts-BRAzC6W1.js.map +0 -1
- package/build/server/chunks/_server.ts-bk_EeAdY.js.map +0 -1
- package/build/server/chunks/guest-registry-t0-7Zv5q.js.map +0 -1
|
@@ -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;
|
|
@@ -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,73 @@
|
|
|
1
|
+
// Hand-written autopilot types — union/record types not expressible in YAML.
|
|
2
|
+
|
|
3
|
+
/** A raw proposal from a single agent. */
|
|
4
|
+
export interface AgentProposal {
|
|
5
|
+
confidence: number;
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Autopilot module-level state (not a Svelte store — held in the autopilot module). */
|
|
10
|
+
export interface AutopilotState {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
status: Record<string, AutopilotStatus>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Per-session autopilot lifecycle state. */
|
|
16
|
+
export type AutopilotStatus = 'error' | 'idle' | 'running';
|
|
17
|
+
|
|
18
|
+
/** Result from mergeNextStepConsensus(). */
|
|
19
|
+
export interface ConsensusResult {
|
|
20
|
+
/** Number of agent lists passed in. */
|
|
21
|
+
agentCount: number;
|
|
22
|
+
/** Quorum threshold used. */
|
|
23
|
+
quorum: number;
|
|
24
|
+
/** Consensus (or tentative) next-step list, sorted by votes desc then confidence desc. */
|
|
25
|
+
steps: NextStep[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Options for mergeNextStepConsensus(). */
|
|
29
|
+
export interface MergeOptions {
|
|
30
|
+
/** Max steps taken from each agent list (default 3). */
|
|
31
|
+
k?: number;
|
|
32
|
+
/** Minimum vote count for a group to reach consensus (default 3). */
|
|
33
|
+
quorum?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A single proposed next step from one agent or the merged consensus. */
|
|
37
|
+
export interface NextStep {
|
|
38
|
+
/** Confidence score in [0, 1]. For consensus steps this is the mean across proposers. */
|
|
39
|
+
confidence: number;
|
|
40
|
+
/** Present and true when no group reached quorum; indicates low-certainty result. */
|
|
41
|
+
tentative?: boolean;
|
|
42
|
+
/** The original (highest-confidence) phrasing of the step. */
|
|
43
|
+
text: string;
|
|
44
|
+
/** Number of distinct agents that proposed this step (consensus only). */
|
|
45
|
+
votes?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Additional per-session fields added by the autopilot engine.
|
|
50
|
+
* These are optional so that SessionState objects (which lack them by default)
|
|
51
|
+
* remain assignable; the engine writes them via Svelte 5's reactive proxy.
|
|
52
|
+
*/
|
|
53
|
+
export interface SessionAutopilotFields {
|
|
54
|
+
/** ISO 8601 timestamp of the last completed autopilot pipeline run. */
|
|
55
|
+
autopilotLastRun?: null | string;
|
|
56
|
+
/** Current autopilot pipeline status for this session. */
|
|
57
|
+
autopilotStatus?: AutopilotStatus;
|
|
58
|
+
/** Consensus next-step list from the last pipeline run. */
|
|
59
|
+
nextSteps?: NextStep[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A persisted summary record stored in session_summaries. */
|
|
63
|
+
export interface SessionSummaryRecord {
|
|
64
|
+
createdAt: string;
|
|
65
|
+
id: string;
|
|
66
|
+
/** JSON-serialised NextStep[] */
|
|
67
|
+
nextSteps: string;
|
|
68
|
+
projectName: null | string;
|
|
69
|
+
sessionId: null | string;
|
|
70
|
+
summary: string;
|
|
71
|
+
terminalId: null | string;
|
|
72
|
+
trigger: string;
|
|
73
|
+
}
|
package/src/lib/types/index.ts
CHANGED
|
@@ -5,5 +5,7 @@ import type { LayoutServerLoad } from './$types';
|
|
|
5
5
|
|
|
6
6
|
export const load: LayoutServerLoad = () => ({
|
|
7
7
|
aiProviders: getProviderAvailability(env),
|
|
8
|
+
litellmBaseUrl: env.LITELLM_BASE_URL ?? '',
|
|
9
|
+
litellmModel: env.LITELLM_MODEL ?? 'open-large',
|
|
8
10
|
neurolinkProvider: env.NEUROLINK_PROVIDER ?? '',
|
|
9
11
|
});
|
|
@@ -22,11 +22,26 @@
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
(window as unknown as Record<string, unknown>).__aiProviders = data.aiProviders;
|
|
25
|
+
|
|
26
|
+
// Ensure window.process.env exists minimally so env vars can be injected.
|
|
27
|
+
const win = window as unknown as Record<string, unknown>;
|
|
28
|
+
if (!win.process || typeof win.process !== 'object') {
|
|
29
|
+
win.process = { env: {} };
|
|
30
|
+
}
|
|
31
|
+
const proc = win.process as Record<string, unknown>;
|
|
32
|
+
if (!proc.env || typeof proc.env !== 'object') {
|
|
33
|
+
proc.env = {};
|
|
34
|
+
}
|
|
35
|
+
const procEnv = proc.env as Record<string, string>;
|
|
36
|
+
|
|
25
37
|
if (data.neurolinkProvider) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
procEnv.NEUROLINK_PROVIDER = data.neurolinkProvider;
|
|
39
|
+
}
|
|
40
|
+
if (data.litellmBaseUrl) {
|
|
41
|
+
procEnv.LITELLM_BASE_URL = data.litellmBaseUrl;
|
|
42
|
+
}
|
|
43
|
+
if (data.litellmModel) {
|
|
44
|
+
procEnv.LITELLM_MODEL = data.litellmModel;
|
|
30
45
|
}
|
|
31
46
|
});
|
|
32
47
|
|
package/src/routes/+page.svelte
CHANGED
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
isShooterConfig,
|
|
13
13
|
setCache,
|
|
14
14
|
} from '$lib/modules/client/common';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
AutopilotPanel,
|
|
17
|
+
connect,
|
|
18
|
+
DashboardView,
|
|
19
|
+
disconnect,
|
|
20
|
+
getCards,
|
|
21
|
+
} from '$lib/modules/client/dashboard';
|
|
16
22
|
import { Banner, Button, EmptyState, Icon, Pill, Shimmer } from '@juspay/svelte-ui-components';
|
|
17
23
|
import { onDestroy, onMount } from 'svelte';
|
|
18
24
|
|
|
@@ -203,6 +209,8 @@
|
|
|
203
209
|
<Button classes="btn-primary" onclick={navigateToConfig} text="Configure Settings" />
|
|
204
210
|
</EmptyState>
|
|
205
211
|
{:else}
|
|
212
|
+
<AutopilotPanel />
|
|
213
|
+
|
|
206
214
|
<!-- Dashboard section: active terminal sessions -->
|
|
207
215
|
{#if cards.length > 0}
|
|
208
216
|
<div class="dashboard-section">
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Control endpoint for the always-on server-side autopilot engine.
|
|
2
|
+
// GET returns the enabled flag; POST { enabled } toggles it.
|
|
3
|
+
//
|
|
4
|
+
// The engine runs in the server.ts module graph and exposes its control on
|
|
5
|
+
// globalThis.__shooter_autopilot. This route (bundled handler graph) reaches it
|
|
6
|
+
// there rather than importing the engine — importing it would start a second
|
|
7
|
+
// event subscriber. Falls back to the on-disk state file if the engine has not
|
|
8
|
+
// started yet.
|
|
9
|
+
|
|
10
|
+
import { validateAuth } from '$lib/modules/server/auth';
|
|
11
|
+
import { json } from '@sveltejs/kit';
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
|
|
16
|
+
import type { RequestHandler } from './$types';
|
|
17
|
+
|
|
18
|
+
const STATE_FILE = join(homedir(), '.shooter', 'autopilot.json');
|
|
19
|
+
|
|
20
|
+
function control(): undefined | { isEnabled: () => boolean; setEnabled: (v: boolean) => void } {
|
|
21
|
+
return (globalThis as Record<string, unknown>).__shooter_autopilot as
|
|
22
|
+
| undefined
|
|
23
|
+
| { isEnabled: () => boolean; setEnabled: (v: boolean) => void };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readFileEnabled(): boolean {
|
|
27
|
+
try {
|
|
28
|
+
if (existsSync(STATE_FILE)) {
|
|
29
|
+
const parsed: unknown = JSON.parse(readFileSync(STATE_FILE, 'utf-8'));
|
|
30
|
+
return Boolean((parsed as { enabled?: unknown })?.enabled);
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// corrupt / unreadable
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const GET: RequestHandler = ({ request }) => {
|
|
39
|
+
const authError = validateAuth(request);
|
|
40
|
+
if (authError) {
|
|
41
|
+
return authError;
|
|
42
|
+
}
|
|
43
|
+
const ctrl = control();
|
|
44
|
+
return json({ enabled: ctrl ? ctrl.isEnabled() : readFileEnabled(), running: Boolean(ctrl) });
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
48
|
+
const authError = validateAuth(request);
|
|
49
|
+
if (authError) {
|
|
50
|
+
return authError;
|
|
51
|
+
}
|
|
52
|
+
let body: { enabled?: unknown };
|
|
53
|
+
try {
|
|
54
|
+
body = (await request.json()) as { enabled?: unknown };
|
|
55
|
+
} catch {
|
|
56
|
+
return json({ error: 'Invalid JSON body' }, { status: 400 });
|
|
57
|
+
}
|
|
58
|
+
if (typeof body.enabled !== 'boolean') {
|
|
59
|
+
return json({ error: 'enabled must be a boolean' }, { status: 400 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const ctrl = control();
|
|
63
|
+
if (ctrl) {
|
|
64
|
+
ctrl.setEnabled(body.enabled);
|
|
65
|
+
} else {
|
|
66
|
+
try {
|
|
67
|
+
mkdirSync(join(homedir(), '.shooter'), { recursive: true });
|
|
68
|
+
writeFileSync(STATE_FILE, JSON.stringify({ enabled: body.enabled }), 'utf-8');
|
|
69
|
+
} catch {
|
|
70
|
+
// best-effort
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return json({ enabled: body.enabled, running: Boolean(ctrl) });
|
|
74
|
+
};
|
|
@@ -15,6 +15,7 @@ import type { RequestHandler } from './$types';
|
|
|
15
15
|
const ALLOWED_CLIENT_HEADERS: Record<string, Set<string>> = {
|
|
16
16
|
anthropic: new Set(['anthropic-beta', 'anthropic-version']),
|
|
17
17
|
'google-ai': new Set<string>([]),
|
|
18
|
+
litellm: new Set<string>([]),
|
|
18
19
|
mistral: new Set([]),
|
|
19
20
|
openai: new Set(['openai-organization', 'openai-project']),
|
|
20
21
|
};
|
|
@@ -63,15 +64,29 @@ export const POST: RequestHandler = async ({ request }) => {
|
|
|
63
64
|
openai: 'https://api.openai.com/',
|
|
64
65
|
};
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
if (provider === 'litellm') {
|
|
68
|
+
// SSRF-safe: only forward to the operator-configured LiteLLM endpoint.
|
|
69
|
+
// Trim to align with getProviderAvailability semantics (whitespace-only = not configured).
|
|
70
|
+
const rawBase = env.LITELLM_BASE_URL?.trim();
|
|
71
|
+
if (!rawBase) {
|
|
72
|
+
return json({ error: 'LiteLLM is not configured on this server' }, { status: 403 });
|
|
73
|
+
}
|
|
74
|
+
const litellmPrefix = `${rawBase.replace(/\/+$/, '')}/`;
|
|
75
|
+
if (!url.startsWith(litellmPrefix)) {
|
|
76
|
+
return json({ error: `Provider "${provider}" or URL not allowed` }, { status: 403 });
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
const allowedPrefix = ALLOWED_PREFIXES[provider];
|
|
80
|
+
if (!allowedPrefix || !url.startsWith(allowedPrefix)) {
|
|
81
|
+
return json({ error: `Provider "${provider}" or URL not allowed` }, { status: 403 });
|
|
82
|
+
}
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
// Inject the server-side API key so the browser never sees it
|
|
72
86
|
const apiKeyEnv: Record<string, string> = {
|
|
73
87
|
anthropic: env.ANTHROPIC_API_KEY ?? '',
|
|
74
88
|
'google-ai': env.GOOGLE_AI_API_KEY ?? '',
|
|
89
|
+
litellm: env.LITELLM_API_KEY ?? '',
|
|
75
90
|
mistral: env.MISTRAL_API_KEY ?? '',
|
|
76
91
|
openai: env.OPENAI_API_KEY ?? '',
|
|
77
92
|
};
|
|
@@ -93,16 +108,26 @@ export const POST: RequestHandler = async ({ request }) => {
|
|
|
93
108
|
...normalizedReqHeaders,
|
|
94
109
|
};
|
|
95
110
|
|
|
96
|
-
// Override / set auth header with server-side key
|
|
111
|
+
// Override / set auth header with server-side key.
|
|
112
|
+
// For Bearer-token providers, only inject the header when the key is non-empty
|
|
113
|
+
// to avoid sending a malformed `Authorization: Bearer ` to the upstream.
|
|
97
114
|
if (provider === 'anthropic') {
|
|
98
115
|
forwardHeaders['x-api-key'] = apiKeyEnv.anthropic;
|
|
99
116
|
forwardHeaders['anthropic-version'] = forwardHeaders['anthropic-version'] ?? '2023-06-01';
|
|
100
117
|
} else if (provider === 'google-ai') {
|
|
101
118
|
forwardHeaders['x-goog-api-key'] = apiKeyEnv['google-ai'];
|
|
102
119
|
} else if (provider === 'openai') {
|
|
103
|
-
|
|
120
|
+
if (apiKeyEnv.openai) {
|
|
121
|
+
forwardHeaders.Authorization = `Bearer ${apiKeyEnv.openai}`;
|
|
122
|
+
}
|
|
104
123
|
} else if (provider === 'mistral') {
|
|
105
|
-
|
|
124
|
+
if (apiKeyEnv.mistral) {
|
|
125
|
+
forwardHeaders.Authorization = `Bearer ${apiKeyEnv.mistral}`;
|
|
126
|
+
}
|
|
127
|
+
} else if (provider === 'litellm') {
|
|
128
|
+
if (apiKeyEnv.litellm) {
|
|
129
|
+
forwardHeaders.Authorization = `Bearer ${apiKeyEnv.litellm}`;
|
|
130
|
+
}
|
|
106
131
|
}
|
|
107
132
|
|
|
108
133
|
const controller = new AbortController();
|
|
@@ -238,7 +238,15 @@ function isDuplicateNotification(
|
|
|
238
238
|
return false;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
// Autopilot pushes include a stable dedupKey keyed on sessionId + top-step text
|
|
242
|
+
// (not the variable summary). Prefer it over the title|message|category key so
|
|
243
|
+
// two runs with the same top-step but different summary wording are correctly
|
|
244
|
+
// deduplicated.
|
|
245
|
+
const dataRecord = data as (NotificationData & { dedupKey?: string }) | undefined;
|
|
246
|
+
const key = dataRecord?.dedupKey
|
|
247
|
+
? dataRecord.dedupKey
|
|
248
|
+
: `${title}|${message}|${data?.category || 'unknown'}`;
|
|
249
|
+
|
|
242
250
|
const now = Date.now();
|
|
243
251
|
|
|
244
252
|
if (notificationCache.has(key)) {
|
|
@@ -262,7 +270,10 @@ function isDuplicateNotification(
|
|
|
262
270
|
|
|
263
271
|
/** Record a notification key in the dedup cache after successful delivery. */
|
|
264
272
|
function recordNotification(title: string, message: string, data?: NotificationData): void {
|
|
265
|
-
const
|
|
273
|
+
const dataRecord = data as (NotificationData & { dedupKey?: string }) | undefined;
|
|
274
|
+
const key = dataRecord?.dedupKey
|
|
275
|
+
? dataRecord.dedupKey
|
|
276
|
+
: `${title}|${message}|${data?.category || 'unknown'}`;
|
|
266
277
|
notificationCache.set(key, Date.now());
|
|
267
278
|
}
|
|
268
279
|
|
|
@@ -302,6 +313,11 @@ export const POST: RequestHandler = async ({ request }) => {
|
|
|
302
313
|
const waitForResponse =
|
|
303
314
|
typeof body.waitForResponse === 'boolean' ? body.waitForResponse : false;
|
|
304
315
|
|
|
316
|
+
// forcePush: when true, send the push even if WS clients are connected.
|
|
317
|
+
// Used by the autopilot engine for high-signal notifications.
|
|
318
|
+
// Deduplication still applies — only the WS-client skip is bypassed.
|
|
319
|
+
const forcePush = typeof body.forcePush === 'boolean' ? body.forcePush : false;
|
|
320
|
+
|
|
305
321
|
// Dynamic-options fields (PR-2/PR-3). When the caller wants to drive
|
|
306
322
|
// a richer notification category — plan-mode approval, MCP
|
|
307
323
|
// elicitation, AskUserQuestion choices — these arrive in the top
|
|
@@ -375,7 +391,8 @@ export const POST: RequestHandler = async ({ request }) => {
|
|
|
375
391
|
// (for bidirectional permission polling) without actually sending a push
|
|
376
392
|
// notification. This happens when WebSocket clients are connected and the
|
|
377
393
|
// events channel will broadcast the permission-requested event instead.
|
|
378
|
-
|
|
394
|
+
// forcePush overrides this so high-signal autopilot pushes still reach the device.
|
|
395
|
+
if (skipPush && !forcePush) {
|
|
379
396
|
if (waitForResponse) {
|
|
380
397
|
createPendingRequest(canonicalRequestId, {
|
|
381
398
|
options,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { SessionSummaryRecord } from '$lib/types';
|
|
2
|
+
|
|
3
|
+
import { validateAuth } from '$lib/modules/server/auth';
|
|
4
|
+
import { summaryStore } from '$lib/modules/server/sessions/summary-store';
|
|
5
|
+
import { json } from '@sveltejs/kit';
|
|
6
|
+
|
|
7
|
+
import type { RequestHandler } from './$types';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_LIMIT = 50;
|
|
10
|
+
const MAX_LIMIT = 200;
|
|
11
|
+
|
|
12
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
13
|
+
const authError = validateAuth(request);
|
|
14
|
+
if (authError) {
|
|
15
|
+
return authError;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const rawBody: unknown = await request.json();
|
|
19
|
+
|
|
20
|
+
if (!rawBody || typeof rawBody !== 'object' || Array.isArray(rawBody)) {
|
|
21
|
+
return json({ error: 'Request body must be a JSON object' }, { status: 400 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const body = rawBody as Record<string, unknown>;
|
|
25
|
+
|
|
26
|
+
// Validate required fields with length caps to prevent unbounded storage growth.
|
|
27
|
+
if (typeof body.id !== 'string' || !body.id) {
|
|
28
|
+
return json({ error: 'id is required and must be a non-empty string' }, { status: 400 });
|
|
29
|
+
}
|
|
30
|
+
if (body.id.length > 128) {
|
|
31
|
+
return json({ error: 'id must be 128 characters or fewer' }, { status: 400 });
|
|
32
|
+
}
|
|
33
|
+
if (typeof body.summary !== 'string' || !body.summary) {
|
|
34
|
+
return json({ error: 'summary is required and must be a non-empty string' }, { status: 400 });
|
|
35
|
+
}
|
|
36
|
+
if (body.summary.length > 1000) {
|
|
37
|
+
return json({ error: 'summary must be 1000 characters or fewer' }, { status: 400 });
|
|
38
|
+
}
|
|
39
|
+
if (typeof body.trigger !== 'string' || !body.trigger) {
|
|
40
|
+
return json({ error: 'trigger is required and must be a non-empty string' }, { status: 400 });
|
|
41
|
+
}
|
|
42
|
+
if (body.trigger.length > 64) {
|
|
43
|
+
return json({ error: 'trigger must be 64 characters or fewer' }, { status: 400 });
|
|
44
|
+
}
|
|
45
|
+
if (typeof body.createdAt !== 'string' || !body.createdAt) {
|
|
46
|
+
return json({ error: 'createdAt is required and must be a non-empty string' }, { status: 400 });
|
|
47
|
+
}
|
|
48
|
+
if (body.createdAt.length > 64 || isNaN(Date.parse(body.createdAt))) {
|
|
49
|
+
return json({ error: 'createdAt must be a valid ISO 8601 date string' }, { status: 400 });
|
|
50
|
+
}
|
|
51
|
+
if (typeof body.nextSteps === 'string' && body.nextSteps.length > 8192) {
|
|
52
|
+
return json({ error: 'nextSteps must be 8192 characters or fewer' }, { status: 400 });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const record: SessionSummaryRecord = {
|
|
56
|
+
createdAt: body.createdAt,
|
|
57
|
+
id: body.id,
|
|
58
|
+
nextSteps: typeof body.nextSteps === 'string' ? body.nextSteps : '[]',
|
|
59
|
+
projectName: typeof body.projectName === 'string' ? body.projectName : null,
|
|
60
|
+
sessionId: typeof body.sessionId === 'string' ? body.sessionId : null,
|
|
61
|
+
summary: body.summary,
|
|
62
|
+
terminalId: typeof body.terminalId === 'string' ? body.terminalId : null,
|
|
63
|
+
trigger: body.trigger,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
summaryStore.insert(record);
|
|
68
|
+
} catch (err: unknown) {
|
|
69
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70
|
+
// Avoid surfacing raw SQLite error messages (may leak schema details).
|
|
71
|
+
const isDuplicate = typeof message === 'string' && message.includes('UNIQUE constraint failed');
|
|
72
|
+
if (isDuplicate) {
|
|
73
|
+
return json({ error: 'Record with this id already exists' }, { status: 409 });
|
|
74
|
+
}
|
|
75
|
+
return json({ error: 'Failed to persist summary' }, { status: 500 });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return json({ id: record.id, success: true }, { status: 201 });
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const GET: RequestHandler = ({ request, url }) => {
|
|
82
|
+
const authError = validateAuth(request);
|
|
83
|
+
if (authError) {
|
|
84
|
+
return authError;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sessionId = url.searchParams.get('sessionId') ?? undefined;
|
|
88
|
+
const rawLimit = parseInt(url.searchParams.get('limit') || String(DEFAULT_LIMIT));
|
|
89
|
+
const limit =
|
|
90
|
+
Number.isFinite(rawLimit) && rawLimit >= 1 ? Math.min(rawLimit, MAX_LIMIT) : DEFAULT_LIMIT;
|
|
91
|
+
|
|
92
|
+
const summaries = summaryStore.listRecent(limit, sessionId);
|
|
93
|
+
|
|
94
|
+
return json({
|
|
95
|
+
count: summaries.length,
|
|
96
|
+
summaries,
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
});
|
|
99
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@keyframes svelte-1qbe2oo-pulse{0%,to{opacity:1}50%{opacity:.4}}@keyframes svelte-1qbe2oo-activity-pulse{0%,to{transform:scale(1);opacity:1}50%{transform:scale(1.5);opacity:.7}}.card.svelte-1qbe2oo{background:var(--component-bg, #1a1a1a);border:1px solid var(--border, #2e2e2e);border-left:4px solid var(--status-color, #6b7280);border-radius:var(--radius-lg, 8px);padding:var(--space-4, 16px);display:flex;flex-direction:column;gap:var(--space-3, 12px);cursor:pointer;transition:border-color var(--transition-fast, .15s ease),background var(--transition-fast, .15s ease);color:inherit;text-align:left;width:100%}.card.svelte-1qbe2oo:hover{background:var(--component-bg-hover, #1f1f1f);border-color:var(--border-hover, #454545);border-left-color:var(--status-color, #6b7280)}.card.svelte-1qbe2oo:focus-visible{outline:2px solid var(--ds-blue-700, #0070f3);outline-offset:2px}.card-header.svelte-1qbe2oo{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2, 8px);min-width:0}.project-name.svelte-1qbe2oo{font-size:var(--text-base, 14px);font-weight:600;color:var(--text-primary, #ededed);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1}.status-pill{--pill-font-size: var(--text-xs, 12px);--pill-font-weight: 500;--pill-padding: 3px 10px;--pill-border-radius: var(--radius-full, 9999px);--pill-cursor: default;flex-shrink:0}.status-pill--running-active{--pill-background: var(--ds-green-100);--pill-color: var(--ds-green-700);--pill-hover-background: var(--ds-green-100);--pill-hover-color: var(--ds-green-700);animation:svelte-1qbe2oo-pulse 2s ease-in-out infinite}.status-pill--running{--pill-background: var(--ds-blue-100);--pill-color: var(--ds-blue-700);--pill-hover-background: var(--ds-blue-100);--pill-hover-color: var(--ds-blue-700)}.status-pill--idle{--pill-background: var(--ds-amber-100);--pill-color: var(--ds-amber-700);--pill-hover-background: var(--ds-amber-100);--pill-hover-color: var(--ds-amber-700)}.status-pill--exited{--pill-background: var(--ds-gray-alpha-200);--pill-color: var(--ds-gray-600);--pill-hover-background: var(--ds-gray-alpha-200);--pill-hover-color: var(--ds-gray-600)}.status-pill--error{--pill-background: var(--ds-red-100);--pill-color: var(--ds-red-700);--pill-hover-background: var(--ds-red-100);--pill-hover-color: var(--ds-red-700)}.goal-text.svelte-1qbe2oo{font-size:var(--text-sm, 13px);color:var(--text-secondary, #a1a1a1);font-style:italic;margin:0;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;line-height:var(--leading-normal, 1.5)}.summary-row.svelte-1qbe2oo{min-height:18px}.summary-text.svelte-1qbe2oo{font-size:var(--text-sm, 13px);color:var(--text-primary, #ededed);line-height:var(--leading-normal, 1.5);display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.summarizing.svelte-1qbe2oo{font-size:var(--text-xs, 12px);color:var(--ds-amber-700);font-style:italic}.summary-fallback.svelte-1qbe2oo{font-size:var(--text-xs, 12px);color:var(--text-tertiary, #7d7d7d);font-style:italic}.stats-row.svelte-1qbe2oo{display:flex;align-items:center;gap:var(--space-3, 12px);flex-wrap:wrap;min-width:0}.stat.svelte-1qbe2oo{display:inline-flex;align-items:center;gap:var(--space-1, 4px);font-size:var(--text-xs, 12px);color:var(--text-tertiary, #7d7d7d);white-space:nowrap}.stat-error.svelte-1qbe2oo{color:var(--ds-red-700)}.stat-path.svelte-1qbe2oo{font-family:var(--font-mono, monospace);overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1;text-align:right}.status-dot-active.svelte-1qbe2oo{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--ds-green-700);animation:svelte-1qbe2oo-activity-pulse .6s ease-in-out infinite;flex-shrink:0}.status-dot-static.svelte-1qbe2oo{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--ds-gray-600, #878787);flex-shrink:0}@media(max-width:768px){.card.svelte-1qbe2oo{padding:10px 12px}.stats-row.svelte-1qbe2oo{flex-wrap:wrap;gap:4px}}@media(max-width:480px){.card.svelte-1qbe2oo{padding:8px 10px}.goal-text.svelte-1qbe2oo{-webkit-line-clamp:1;line-clamp:1}}.section.svelte-14sxgtb{display:flex;flex-direction:column;gap:var(--space-3, 12px);margin-bottom:var(--space-6, 24px)}.section-label.svelte-14sxgtb{font-size:var(--text-xs, 12px);font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--text-secondary, #a1a1a1);margin:0 0 var(--space-1, 4px) 0}.stats-bar.svelte-1uha8ag{display:flex;gap:10px;margin-top:var(--space-4)}.stat-chip.svelte-1uha8ag{display:flex;align-items:center;gap:6px;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:20px;padding:6px 14px}.stat-chip-active.svelte-1uha8ag{background:var(--ds-green-alpha-200);border-color:var(--ds-green-alpha-400)}.stat-value.svelte-1uha8ag{font-weight:700;font-size:.9rem;color:#f0f0f0}.stat-chip-active.svelte-1uha8ag .stat-value:where(.svelte-1uha8ag){color:var(--ds-green-500)}.stat-label.svelte-1uha8ag{font-size:.78rem;color:#a3a3a3cc}.dashboard-section.svelte-1uha8ag{margin-bottom:var(--space-6)}.section-label.svelte-1uha8ag{font-size:var(--text-xs, 12px);font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--text-secondary);margin-bottom:var(--space-3)}.projects-container.svelte-1uha8ag{display:flex;flex-direction:column;gap:var(--space-3);animation:fadeIn .2s ease}@media(max-width:768px){.stats-bar.svelte-1uha8ag{flex-wrap:wrap}}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|