@juspay/shooter 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/notifier.cjs +94 -1
- package/build/client/_app/immutable/assets/2.JWRrnR-w.css +1 -0
- package/build/client/_app/immutable/assets/2.JWRrnR-w.css.br +0 -0
- package/build/client/_app/immutable/assets/2.JWRrnR-w.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{DOEXXmsh.js → Bj5wFimK.js} +2 -2
- package/build/client/_app/immutable/chunks/Bj5wFimK.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bj5wFimK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{EqMAkEha.js → BjYr_-Ss.js} +1 -1
- package/build/client/_app/immutable/chunks/BjYr_-Ss.js.br +0 -0
- package/build/client/_app/immutable/chunks/BjYr_-Ss.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C4Hns_Wl.js +1 -0
- package/build/client/_app/immutable/chunks/C4Hns_Wl.js.br +0 -0
- package/build/client/_app/immutable/chunks/C4Hns_Wl.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DULfdsh6.js +6 -0
- package/build/client/_app/immutable/chunks/DULfdsh6.js.br +0 -0
- package/build/client/_app/immutable/chunks/DULfdsh6.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BmfLecb1.js → fcNfTA-E.js} +1 -1
- package/build/client/_app/immutable/chunks/fcNfTA-E.js.br +0 -0
- package/build/client/_app/immutable/chunks/fcNfTA-E.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CeSxgGat.js → app.Bvoqymnp.js} +2 -2
- package/build/client/_app/immutable/entry/app.Bvoqymnp.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Bvoqymnp.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BqXCPPZJ.js +1 -0
- package/build/client/_app/immutable/entry/start.BqXCPPZJ.js.br +2 -0
- package/build/client/_app/immutable/entry/start.BqXCPPZJ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.oaPwxh1O.js → 0.Bv_TwEnq.js} +1 -1
- package/build/client/_app/immutable/nodes/0.Bv_TwEnq.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Bv_TwEnq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.DMPyoM-M.js → 1.7lffTIeb.js} +1 -1
- package/build/client/_app/immutable/nodes/1.7lffTIeb.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.7lffTIeb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{10.Cbm7nQKK.js → 10.ChiIrIDl.js} +1 -1
- package/build/client/_app/immutable/nodes/10.ChiIrIDl.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.ChiIrIDl.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{11.CKmZjP_a.js → 11.DO3vyXEv.js} +2 -2
- package/build/client/_app/immutable/nodes/11.DO3vyXEv.js.br +0 -0
- package/build/client/_app/immutable/nodes/{11.CKmZjP_a.js.gz → 11.DO3vyXEv.js.gz} +0 -0
- package/build/client/_app/immutable/nodes/2.iMIqsE7n.js +23 -0
- package/build/client/_app/immutable/nodes/2.iMIqsE7n.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.iMIqsE7n.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.BgLpGnzb.js → 3.CArnSHOO.js} +1 -1
- package/build/client/_app/immutable/nodes/3.CArnSHOO.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CArnSHOO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{5.Avc1-gVb.js → 5.DziEu9rx.js} +1 -1
- package/build/client/_app/immutable/nodes/5.DziEu9rx.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.DziEu9rx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.Dw2wEssJ.js → 6.B8l1RwkB.js} +1 -1
- package/build/client/_app/immutable/nodes/6.B8l1RwkB.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.B8l1RwkB.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.DwKZjoBg.js → 7.BPyfhDis.js} +1 -1
- package/build/client/_app/immutable/nodes/7.BPyfhDis.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.BPyfhDis.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.ZUAI6g5E.js → 8.D_vszZ9E.js} +1 -1
- package/build/client/_app/immutable/nodes/8.D_vszZ9E.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.D_vszZ9E.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.I_KGXPwB.js → 9.Drah-do-.js} +1 -1
- package/build/client/_app/immutable/nodes/9.Drah-do-.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.Drah-do-.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/pty-holder.cjs +6 -0
- package/build/server/chunks/{0-vrTNAfZB.js → 0-DAB_6Vm1.js} +2 -2
- package/build/server/chunks/{0-vrTNAfZB.js.map → 0-DAB_6Vm1.js.map} +1 -1
- package/build/server/chunks/{1-nbr-bOoF.js → 1-D-qMYaCx.js} +2 -2
- package/build/server/chunks/{1-nbr-bOoF.js.map → 1-D-qMYaCx.js.map} +1 -1
- package/build/server/chunks/{10-ChyvvJ6w.js → 10-CeFFGo-X.js} +2 -2
- package/build/server/chunks/{10-ChyvvJ6w.js.map → 10-CeFFGo-X.js.map} +1 -1
- package/build/server/chunks/{11-6ZAjL3uU.js → 11-DRMu_ATU.js} +2 -2
- package/build/server/chunks/{11-6ZAjL3uU.js.map → 11-DRMu_ATU.js.map} +1 -1
- package/build/server/chunks/{2-DWFRVDWJ.js → 2-B7OLBMNH.js} +4 -4
- package/build/server/chunks/{2-DWFRVDWJ.js.map → 2-B7OLBMNH.js.map} +1 -1
- package/build/server/chunks/{3-CKANM_WM.js → 3-B38ZarLw.js} +2 -2
- package/build/server/chunks/{3-CKANM_WM.js.map → 3-B38ZarLw.js.map} +1 -1
- package/build/server/chunks/{5-BxVjs2qi.js → 5-D-Uv1voC.js} +2 -2
- package/build/server/chunks/{5-BxVjs2qi.js.map → 5-D-Uv1voC.js.map} +1 -1
- package/build/server/chunks/{6-Cbf1AAMQ.js → 6-DP46cUej.js} +2 -2
- package/build/server/chunks/{6-Cbf1AAMQ.js.map → 6-DP46cUej.js.map} +1 -1
- package/build/server/chunks/{7-CMK2quEf.js → 7-B29_3ar6.js} +2 -2
- package/build/server/chunks/{7-CMK2quEf.js.map → 7-B29_3ar6.js.map} +1 -1
- package/build/server/chunks/{8-DhdfkfDM.js → 8-DCnSDVrX.js} +2 -2
- package/build/server/chunks/{8-DhdfkfDM.js.map → 8-DCnSDVrX.js.map} +1 -1
- package/build/server/chunks/{9-CPpxtRM5.js → 9-BwqDc8wC.js} +2 -2
- package/build/server/chunks/{9-CPpxtRM5.js.map → 9-BwqDc8wC.js.map} +1 -1
- package/build/server/chunks/_page.svelte-8OFzwdNA.js +758 -0
- package/build/server/chunks/_page.svelte-8OFzwdNA.js.map +1 -0
- package/build/server/chunks/{_server.ts-BWVlO8iV.js → _server.ts-05JJOdcX.js} +15 -12
- package/build/server/chunks/_server.ts-05JJOdcX.js.map +1 -0
- package/build/server/chunks/{_server.ts-BevnuePu.js → _server.ts-BCljU9Sg.js} +7 -3
- package/build/server/chunks/_server.ts-BCljU9Sg.js.map +1 -0
- package/build/server/chunks/{_server.ts-D-vgx5UZ.js → _server.ts-BTmknWpO.js} +2 -2
- package/build/server/chunks/{_server.ts-D-vgx5UZ.js.map → _server.ts-BTmknWpO.js.map} +1 -1
- package/build/server/chunks/{_server.ts-tChyh9FX.js → _server.ts-BXhmLZwN.js} +4 -2
- package/build/server/chunks/{_server.ts-tChyh9FX.js.map → _server.ts-BXhmLZwN.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CvJKTS3Z.js → _server.ts-BbRSpB74.js} +4 -2
- package/build/server/chunks/{_server.ts-CvJKTS3Z.js.map → _server.ts-BbRSpB74.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CC2K8-L2.js → _server.ts-Blx6TuRU.js} +4 -2
- package/build/server/chunks/_server.ts-Blx6TuRU.js.map +1 -0
- package/build/server/chunks/_server.ts-C6NRpe7e.js +33 -0
- package/build/server/chunks/_server.ts-C6NRpe7e.js.map +1 -0
- package/build/server/chunks/_server.ts-CGqCOCdK.js +53 -0
- package/build/server/chunks/_server.ts-CGqCOCdK.js.map +1 -0
- package/build/server/chunks/{_server.ts-X1R7L_QI.js → _server.ts-CYWXjihn.js} +4 -2
- package/build/server/chunks/{_server.ts-X1R7L_QI.js.map → _server.ts-CYWXjihn.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CD7JP3fz.js → _server.ts-D0___krA.js} +4 -2
- package/build/server/chunks/_server.ts-D0___krA.js.map +1 -0
- package/build/server/chunks/{_server.ts-VzDcFFgy.js → _server.ts-DPHRUFYS.js} +4 -2
- package/build/server/chunks/_server.ts-DPHRUFYS.js.map +1 -0
- package/build/server/chunks/{_server.ts-D0zRDSx0.js → _server.ts-D_WRex0k.js} +4 -2
- package/build/server/chunks/_server.ts-D_WRex0k.js.map +1 -0
- package/build/server/chunks/{_server.ts-CA5KUENM.js → _server.ts-Da1kSClZ.js} +4 -2
- package/build/server/chunks/_server.ts-Da1kSClZ.js.map +1 -0
- package/build/server/chunks/{_server.ts-Dp-hXW_I.js → _server.ts-l3cd4Cto.js} +4 -2
- package/build/server/chunks/_server.ts-l3cd4Cto.js.map +1 -0
- package/build/server/chunks/{library-apns-Dl3iRE2h.js → library-apns-D8RPINlv.js} +62 -7
- package/build/server/chunks/library-apns-D8RPINlv.js.map +1 -0
- package/build/server/chunks/{pending-requests-C9p57WoU.js → pending-requests-8rWjrF6d.js} +3 -2
- package/build/server/chunks/pending-requests-8rWjrF6d.js.map +1 -0
- package/build/server/chunks/presence-store-Bx_g0-Gd.js +23 -0
- package/build/server/chunks/presence-store-Bx_g0-Gd.js.map +1 -0
- package/build/server/chunks/{pty-manager-ZqXqa-6A.js → pty-manager-DDjG7DlH.js} +297 -31
- package/build/server/chunks/pty-manager-DDjG7DlH.js.map +1 -0
- package/build/server/chunks/shooter-home-4f_HkdGI.js +10 -0
- package/build/server/chunks/shooter-home-4f_HkdGI.js.map +1 -0
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +38 -24
- package/build/server/manifest.js.map +1 -1
- package/package.json +4 -2
- package/server.ts +2 -2
- package/src/lib/modules/client/common/index.ts +1 -0
- package/src/lib/modules/client/common/presence.ts +47 -0
- package/src/lib/modules/client/dashboard/AutopilotPanel.svelte +188 -4
- package/src/lib/modules/client/dashboard/autopilot-driver.svelte.ts +681 -0
- package/src/lib/modules/client/dashboard/decide-injection.ts +127 -0
- package/src/lib/modules/client/dashboard/store.svelte.ts +65 -24
- package/src/lib/modules/client/neurolink/fetch-proxy.ts +38 -1
- package/src/lib/modules/client/terminal/xterm-wrapper.ts +52 -12
- package/src/lib/modules/server/apn/apns-payload.ts +50 -0
- package/src/lib/modules/server/apn/library-apns.ts +50 -8
- package/src/lib/modules/server/apn/pending-requests.ts +3 -1
- package/src/lib/modules/server/sessions/autopilot-context.ts +57 -0
- package/src/lib/modules/server/sessions/autopilot-engine.ts +148 -43
- package/src/lib/modules/server/sessions/litellm-client.ts +90 -34
- package/src/lib/modules/server/sessions/next-step-consensus.ts +27 -2
- package/src/lib/modules/server/sessions/summary-store.ts +3 -1
- package/src/lib/modules/server/terminal/agent-launch.ts +26 -0
- package/src/lib/modules/server/terminal/pty-holder.cjs +6 -0
- package/src/lib/modules/server/terminal/pty-manager.ts +292 -38
- package/src/lib/modules/server/terminal/session-watcher.ts +12 -2
- package/src/lib/modules/server/terminal/terminal-emulator.ts +102 -0
- package/src/lib/modules/server/terminal/terminal-store.ts +3 -1
- package/src/lib/modules/server/utils/shooter-home.ts +16 -0
- package/src/lib/modules/server/ws/presence-store.ts +50 -0
- package/src/lib/modules/server/ws/server.ts +18 -2
- package/src/lib/modules/server/ws/terminal-handler.ts +11 -6
- package/src/lib/types/autopilot.ts +65 -0
- package/src/lib/types/generated/WsProtocol.ts +10 -1
- package/src/lib/types/server.ts +27 -1
- package/src/lib/types/terminal-client.ts +3 -0
- package/src/lib/types/ws.ts +3 -2
- package/src/routes/api/autopilot/goal/+server.ts +72 -0
- package/src/routes/api/neurolink-proxy/+server.ts +8 -2
- package/src/routes/api/notify/+server.ts +22 -15
- package/src/routes/api/presence/+server.ts +39 -0
- package/src/routes/api/ws-status/+server.ts +8 -5
- package/build/client/_app/immutable/assets/2.BHi6pjT2.css +0 -1
- 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/BmfLecb1.js.br +0 -0
- package/build/client/_app/immutable/chunks/BmfLecb1.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CRkG7oE4.js +0 -1
- package/build/client/_app/immutable/chunks/CRkG7oE4.js.br +0 -0
- package/build/client/_app/immutable/chunks/CRkG7oE4.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DOEXXmsh.js.br +0 -0
- package/build/client/_app/immutable/chunks/DOEXXmsh.js.gz +0 -0
- package/build/client/_app/immutable/chunks/EqMAkEha.js.br +0 -0
- package/build/client/_app/immutable/chunks/EqMAkEha.js.gz +0 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js +0 -6
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js.br +0 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js.gz +0 -0
- package/build/client/_app/immutable/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 +0 -1
- package/build/client/_app/immutable/entry/start.DrnJFwxA.js.br +0 -2
- package/build/client/_app/immutable/entry/start.DrnJFwxA.js.gz +0 -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.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.Cbm7nQKK.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.Cbm7nQKK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.CKmZjP_a.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.zlrdNFtH.js +0 -13
- 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.BgLpGnzb.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.BgLpGnzb.js.gz +0 -0
- 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.Dw2wEssJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.Dw2wEssJ.js.gz +0 -0
- 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.ZUAI6g5E.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.ZUAI6g5E.js.gz +0 -0
- 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/server/chunks/_page.svelte-tBuIq8Pg.js +0 -159
- package/build/server/chunks/_page.svelte-tBuIq8Pg.js.map +0 -1
- package/build/server/chunks/_server.ts-BWVlO8iV.js.map +0 -1
- package/build/server/chunks/_server.ts-BevnuePu.js.map +0 -1
- package/build/server/chunks/_server.ts-CA5KUENM.js.map +0 -1
- package/build/server/chunks/_server.ts-CC2K8-L2.js.map +0 -1
- package/build/server/chunks/_server.ts-CD7JP3fz.js.map +0 -1
- package/build/server/chunks/_server.ts-D0zRDSx0.js.map +0 -1
- package/build/server/chunks/_server.ts-Dp-hXW_I.js.map +0 -1
- package/build/server/chunks/_server.ts-VzDcFFgy.js.map +0 -1
- package/build/server/chunks/library-apns-Dl3iRE2h.js.map +0 -1
- package/build/server/chunks/pending-requests-C9p57WoU.js.map +0 -1
- package/build/server/chunks/pty-manager-ZqXqa-6A.js.map +0 -1
|
@@ -12,11 +12,18 @@
|
|
|
12
12
|
import type { AgentProposal, NextStep, SessionSummaryRecord, WireShooterEvent } from '$lib/types';
|
|
13
13
|
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
|
-
import { homedir } from 'os';
|
|
16
15
|
import { join } from 'path';
|
|
17
16
|
|
|
18
17
|
import { ptyManager } from '../terminal/pty-manager.js';
|
|
18
|
+
import { shooterDataDir } from '../utils/shooter-home.js';
|
|
19
19
|
import { onShooterEvent } from '../ws/events-handler.js';
|
|
20
|
+
import { isViewerPresent } from '../ws/presence-store.js';
|
|
21
|
+
import {
|
|
22
|
+
buildEngineContext,
|
|
23
|
+
clearEngineGoal,
|
|
24
|
+
getEngineGoal,
|
|
25
|
+
setEngineGoal,
|
|
26
|
+
} from './autopilot-context.js';
|
|
20
27
|
import { isLiteLLMConfigured, litellmJson } from './litellm-client.js';
|
|
21
28
|
import { mergeNextStepConsensus } from './next-step-consensus.js';
|
|
22
29
|
import { summaryStore } from './summary-store.js';
|
|
@@ -29,27 +36,57 @@ const MIN_INTERVAL_MS = 30_000; // minimum gap between pipeline runs per session
|
|
|
29
36
|
const PERIODIC_EVERY = 20;
|
|
30
37
|
const ERROR_THRESHOLD = 3;
|
|
31
38
|
const MAX_EVENTS = 60;
|
|
32
|
-
const SUMMARY_MAX_TOKENS =
|
|
33
|
-
const STEPS_MAX_TOKENS =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
const SUMMARY_MAX_TOKENS = 400;
|
|
40
|
+
const STEPS_MAX_TOKENS = 400;
|
|
41
|
+
// How many of the five lenses run concurrently. Kept BELOW the typical LiteLLM key parallel cap so
|
|
42
|
+
// the engine never saturates the key by itself: firing all five at once exhausted a
|
|
43
|
+
// max_parallel_requests=5 key and 429'd every call (empty consensus, silent stall). Default 3
|
|
44
|
+
// leaves headroom for the summary's retry + other callers sharing the key. Tune per key via env.
|
|
45
|
+
const LENS_CONCURRENCY = Math.max(1, Number(process.env.AUTOPILOT_LENS_CONCURRENCY) || 3);
|
|
46
|
+
// Consensus quorum: how many of the 5 distinct lenses must agree for a step to be non-tentative
|
|
47
|
+
// (and thus auto-injectable). The lenses are DIFFERENT perspectives that phrase the same action
|
|
48
|
+
// differently, so the Jaccard clustering systematically undercounts true agreement (observed: 4/5
|
|
49
|
+
// lenses proposed the same fix but only 2 clustered). 2-of-5 + the confidence floor + the eight
|
|
50
|
+
// driver guards (idle-only, managed-only, rate-limit, dedup, circuit-breaker, command-guard,
|
|
51
|
+
// kill-switch, human-grace) is the practical bar; quorum 3 left the autopilot almost never firing.
|
|
52
|
+
const CONSENSUS_QUORUM = 2;
|
|
53
|
+
const STATE_FILE = join(shooterDataDir(), 'autopilot.json');
|
|
54
|
+
|
|
55
|
+
// The engine does STRUCTURED extraction (one summary + five voting lenses). A reasoning model like
|
|
56
|
+
// `open-large` does this badly: it reasons out loud and ignores response_format, so the JSON never
|
|
57
|
+
// parses and the consensus collapses to tentative garbage. Pin a fast NON-reasoning model for the
|
|
58
|
+
// pipeline, independent of the user's chat model (LITELLM_MODEL). Overridable via AUTOPILOT_MODEL.
|
|
59
|
+
const ENGINE_MODEL = process.env.AUTOPILOT_MODEL?.trim() || 'open-fast';
|
|
60
|
+
|
|
61
|
+
// Lead with the JSON-only contract; the per-lens perspective is a TRAILING modifier so the model
|
|
62
|
+
// doesn't start "thinking" about a role. No copyable placeholder value in the schema — reasoning
|
|
63
|
+
// models echo it verbatim ("a real shell command or instruction" leaked into real consensus).
|
|
64
|
+
const JSON_API_RULES =
|
|
65
|
+
'You are a JSON API. Output ONLY one JSON object and nothing else — no prose, no reasoning, no ' +
|
|
66
|
+
'markdown, no code fences. The first character MUST be { and the last MUST be }.';
|
|
67
|
+
|
|
68
|
+
/** Five DISTINCT perspectives — each a different angle, so converging votes mean real agreement. */
|
|
37
69
|
const LENSES = [
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
70
|
+
'what is currently blocking progress, or is most likely to fail next',
|
|
71
|
+
'the exact shell commands or file edits the agent should run next, in order',
|
|
72
|
+
'what could go wrong if the agent continues on its current path, and what to validate first',
|
|
73
|
+
'how to verify the work so far is correct — the tests, checks, or inspections to run',
|
|
74
|
+
'the single most direct next step toward completing the session goal',
|
|
43
75
|
] as const;
|
|
44
76
|
|
|
45
77
|
// ── Per-session state (globalThis-shared) ───────────────────────────
|
|
46
78
|
|
|
47
79
|
// eslint-disable-next-line no-restricted-syntax -- internal engine state, never exported
|
|
48
80
|
interface EngineSession {
|
|
81
|
+
cancelled: boolean;
|
|
49
82
|
errorCount: number;
|
|
50
83
|
eventCount: number;
|
|
51
84
|
events: string[];
|
|
85
|
+
// Highest signal + latest trigger seen during the OPEN grace window — evaluated at FIRE time, not
|
|
86
|
+
// schedule time, so an agent-idle arriving after a low-signal event still runs as a high trigger.
|
|
87
|
+
graceIsHigh: boolean;
|
|
52
88
|
graceTimer: null | ReturnType<typeof setTimeout>;
|
|
89
|
+
graceTrigger: string;
|
|
53
90
|
lastRunAt: number;
|
|
54
91
|
projectName: string;
|
|
55
92
|
running: boolean;
|
|
@@ -76,7 +113,7 @@ export function isAutopilotEnabled(): boolean {
|
|
|
76
113
|
export function setAutopilotEnabled(value: boolean): void {
|
|
77
114
|
enabled = value;
|
|
78
115
|
try {
|
|
79
|
-
mkdirSync(
|
|
116
|
+
mkdirSync(shooterDataDir(), { recursive: true });
|
|
80
117
|
writeFileSync(STATE_FILE, JSON.stringify({ enabled: value }), 'utf-8');
|
|
81
118
|
} catch {
|
|
82
119
|
// best-effort persistence
|
|
@@ -96,11 +133,30 @@ export function startAutopilotEngine(): void {
|
|
|
96
133
|
if (unsubscribe) {
|
|
97
134
|
return;
|
|
98
135
|
}
|
|
99
|
-
const control = {
|
|
136
|
+
const control = {
|
|
137
|
+
getGoal: getEngineGoal,
|
|
138
|
+
isEnabled: isAutopilotEnabled,
|
|
139
|
+
setEnabled: setAutopilotEnabled,
|
|
140
|
+
setGoal: setEngineGoal,
|
|
141
|
+
};
|
|
100
142
|
(globalThis as Record<string, unknown>).__shooter_autopilot = control;
|
|
101
143
|
unsubscribe = onShooterEvent(handleEvent);
|
|
144
|
+
// Pre-track sessions for terminals that already exist at startup (e.g. a server restart that
|
|
145
|
+
// reconnected persisted terminals). Without this a session is only created lazily on its NEXT
|
|
146
|
+
// event, so an agent that went idle before the engine attached would be invisible until it emits
|
|
147
|
+
// again. We deliberately do NOT force a pipeline run here — that would risk spurious LLM spend on
|
|
148
|
+
// a genuinely quiet terminal; we just ensure the session is tracked so the next event triggers.
|
|
149
|
+
try {
|
|
150
|
+
for (const term of ptyManager.list()) {
|
|
151
|
+
if (!sessions.has(term.id)) {
|
|
152
|
+
createSession(term.id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// ptyManager not ready yet — sessions will be created lazily on first event
|
|
157
|
+
}
|
|
102
158
|
console.log(
|
|
103
|
-
`[autopilot] engine started (enabled=${enabled}, litellm=${isLiteLLMConfigured() ? 'configured' : 'absent'})`
|
|
159
|
+
`[autopilot] engine started (enabled=${enabled}, litellm=${isLiteLLMConfigured() ? 'configured' : 'absent'}, lensConcurrency=${LENS_CONCURRENCY})`
|
|
104
160
|
);
|
|
105
161
|
}
|
|
106
162
|
|
|
@@ -118,7 +174,9 @@ function applyEvent(session: EngineSession, event: WireShooterEvent): void {
|
|
|
118
174
|
parts.push(`cmd=${event.command.slice(0, 80)}`);
|
|
119
175
|
}
|
|
120
176
|
if ('message' in event && event.message) {
|
|
121
|
-
|
|
177
|
+
// The agent's last message on idle (the OUTCOME — e.g. "the test failed because…") is the
|
|
178
|
+
// richest signal the lenses have; keep enough of it to actually ground the next-step votes.
|
|
179
|
+
parts.push(`msg=${event.message.slice(0, 400)}`);
|
|
122
180
|
}
|
|
123
181
|
session.events.push(parts.join(' '));
|
|
124
182
|
if (session.events.length > MAX_EVENTS) {
|
|
@@ -146,10 +204,13 @@ function applyEvent(session: EngineSession, event: WireShooterEvent): void {
|
|
|
146
204
|
|
|
147
205
|
function createSession(terminalId: string): EngineSession {
|
|
148
206
|
const session: EngineSession = {
|
|
207
|
+
cancelled: false,
|
|
149
208
|
errorCount: 0,
|
|
150
209
|
eventCount: 0,
|
|
151
210
|
events: [],
|
|
211
|
+
graceIsHigh: false,
|
|
152
212
|
graceTimer: null,
|
|
213
|
+
graceTrigger: '',
|
|
153
214
|
lastRunAt: 0,
|
|
154
215
|
projectName: projectNameFor(terminalId),
|
|
155
216
|
running: false,
|
|
@@ -165,8 +226,6 @@ function fallbackSummary(session: EngineSession): string {
|
|
|
165
226
|
return `${session.status} — ${session.toolCallCount} tool calls, ${session.errorCount} errors`;
|
|
166
227
|
}
|
|
167
228
|
|
|
168
|
-
// ── Pipeline ─────────────────────────────────────────────────────────
|
|
169
|
-
|
|
170
229
|
function handleEvent(event: WireShooterEvent): void {
|
|
171
230
|
const terminalId = 'terminalId' in event ? event.terminalId : undefined;
|
|
172
231
|
if (!terminalId) {
|
|
@@ -175,10 +234,14 @@ function handleEvent(event: WireShooterEvent): void {
|
|
|
175
234
|
|
|
176
235
|
if (event.type === 'terminal-exited') {
|
|
177
236
|
const s = sessions.get(terminalId);
|
|
178
|
-
if (s
|
|
179
|
-
|
|
237
|
+
if (s) {
|
|
238
|
+
s.cancelled = true; // signal any in-flight pipeline to stop before persist/push
|
|
239
|
+
if (s.graceTimer) {
|
|
240
|
+
clearTimeout(s.graceTimer);
|
|
241
|
+
}
|
|
180
242
|
}
|
|
181
243
|
sessions.delete(terminalId);
|
|
244
|
+
clearEngineGoal(terminalId);
|
|
182
245
|
return;
|
|
183
246
|
}
|
|
184
247
|
|
|
@@ -198,15 +261,44 @@ function handleEvent(event: WireShooterEvent): void {
|
|
|
198
261
|
if (Date.now() - session.lastRunAt < MIN_INTERVAL_MS) {
|
|
199
262
|
return;
|
|
200
263
|
}
|
|
201
|
-
//
|
|
264
|
+
// Track the strongest signal + latest trigger across the whole window (so a high-signal event
|
|
265
|
+
// arriving after the timer was armed by a low-signal one is not lost). Schedule the timer ONCE;
|
|
266
|
+
// do NOT reset it on every event (so a burst doesn't keep delaying the run).
|
|
202
267
|
if (!session.graceTimer) {
|
|
268
|
+
session.graceIsHigh = isHigh;
|
|
269
|
+
session.graceTrigger = event.type;
|
|
203
270
|
session.graceTimer = setTimeout(() => {
|
|
204
271
|
session.graceTimer = null;
|
|
205
|
-
|
|
272
|
+
const trigger = session.graceTrigger;
|
|
273
|
+
const wasHigh = session.graceIsHigh;
|
|
274
|
+
void runPipeline(session, trigger, wasHigh);
|
|
206
275
|
}, GRACE_MS);
|
|
276
|
+
} else {
|
|
277
|
+
session.graceIsHigh = session.graceIsHigh || isHigh;
|
|
278
|
+
session.graceTrigger = event.type;
|
|
207
279
|
}
|
|
208
280
|
}
|
|
209
281
|
|
|
282
|
+
// ── Pipeline ─────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
/** Run `fn` over `items` with at most `limit` in flight; preserves input order. */
|
|
285
|
+
async function mapLimit<T, R>(
|
|
286
|
+
items: readonly T[],
|
|
287
|
+
limit: number,
|
|
288
|
+
fn: (item: T) => Promise<R>
|
|
289
|
+
): Promise<R[]> {
|
|
290
|
+
const results: R[] = new Array<R>(items.length);
|
|
291
|
+
let next = 0;
|
|
292
|
+
const worker = async (): Promise<void> => {
|
|
293
|
+
while (next < items.length) {
|
|
294
|
+
const idx = next++;
|
|
295
|
+
results[idx] = await fn(items[idx]);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
299
|
+
return results;
|
|
300
|
+
}
|
|
301
|
+
|
|
210
302
|
function persist(
|
|
211
303
|
session: EngineSession,
|
|
212
304
|
summary: string,
|
|
@@ -250,6 +342,12 @@ async function push(session: EngineSession, summary: string, steps: NextStep[]):
|
|
|
250
342
|
if (!top) {
|
|
251
343
|
return;
|
|
252
344
|
}
|
|
345
|
+
// Presence-aware: when a viewer is foregrounded (watching the live dashboard) skip the
|
|
346
|
+
// push — they see it in-app. Push only when away. If no presence-aware client ever
|
|
347
|
+
// reported, isViewerPresent() is false → push proceeds (prior always-push behavior).
|
|
348
|
+
if (isViewerPresent()) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
253
351
|
const port = process.env.PORT || '54007';
|
|
254
352
|
const apiKey = process.env.API_KEY;
|
|
255
353
|
if (!apiKey) {
|
|
@@ -300,38 +398,45 @@ async function runPipeline(
|
|
|
300
398
|
session.running = true;
|
|
301
399
|
session.lastRunAt = Date.now();
|
|
302
400
|
try {
|
|
303
|
-
const context =
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
401
|
+
const context = buildEngineContext({
|
|
402
|
+
errorCount: session.errorCount,
|
|
403
|
+
events: session.events,
|
|
404
|
+
goal: getEngineGoal(session.terminalId),
|
|
405
|
+
projectName: session.projectName,
|
|
406
|
+
status: session.status,
|
|
407
|
+
toolCallCount: session.toolCallCount,
|
|
408
|
+
trigger,
|
|
409
|
+
});
|
|
307
410
|
|
|
308
411
|
const summaryResult = await litellmJson<{ summary: string }>({
|
|
309
412
|
maxTokens: SUMMARY_MAX_TOKENS,
|
|
310
|
-
|
|
311
|
-
|
|
413
|
+
model: ENGINE_MODEL,
|
|
414
|
+
systemInstruction: `${JSON_API_RULES} The object has one key "summary": a single sentence (max 120 characters) describing the current status of this coding session.`,
|
|
312
415
|
userPrompt: `${context}\n\nSummarise what is happening in this coding session in ONE sentence (max 120 chars).`,
|
|
313
416
|
});
|
|
314
417
|
const summary = summaryResult?.summary?.trim() || fallbackSummary(session);
|
|
315
418
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
);
|
|
329
|
-
const consensus = mergeNextStepConsensus(agentLists);
|
|
419
|
+
// Run the five lenses at most LENS_CONCURRENCY in flight (default 3) so the engine never
|
|
420
|
+
// saturates the LiteLLM key's parallel cap by itself. Each failed lens yields an empty list;
|
|
421
|
+
// a step needs CONSENSUS_QUORUM (2) of the 5 to be non-tentative.
|
|
422
|
+
const agentLists: AgentProposal[][] = await mapLimit(LENSES, LENS_CONCURRENCY, async (lens) => {
|
|
423
|
+
const r = await litellmJson<{ steps: AgentProposal[] }>({
|
|
424
|
+
maxTokens: STEPS_MAX_TOKENS,
|
|
425
|
+
model: ENGINE_MODEL,
|
|
426
|
+
systemInstruction: `${JSON_API_RULES} The object has one key "steps": an array of 1 to 3 objects, each {"text": the concrete next action for THIS exact session written in full as a string, "confidence": a number between 0 and 1}. Choose the actions from THIS perspective: ${lens}.`,
|
|
427
|
+
userPrompt: `${context}\n\nGiven the session above, what should happen next?`,
|
|
428
|
+
});
|
|
429
|
+
return r?.steps ?? [];
|
|
430
|
+
});
|
|
431
|
+
const consensus = mergeNextStepConsensus(agentLists, { quorum: CONSENSUS_QUORUM });
|
|
330
432
|
|
|
331
|
-
if (!enabled) {
|
|
332
|
-
return;
|
|
433
|
+
if (!enabled || session.cancelled) {
|
|
434
|
+
return; // engine disabled or terminal exited mid-pipeline — don't persist/push a dead session
|
|
333
435
|
}
|
|
334
436
|
persist(session, summary, consensus.steps, trigger);
|
|
437
|
+
// A run consumed the accumulated errors — reset so the error-threshold trigger (errorCount >= 3)
|
|
438
|
+
// does not stick and re-fire on every subsequent event for the rest of the session's life.
|
|
439
|
+
session.errorCount = 0;
|
|
335
440
|
if (isHigh && consensus.steps.length > 0 && !consensus.steps[0].tentative) {
|
|
336
441
|
await push(session, summary, consensus.steps);
|
|
337
442
|
}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// directly with the server-side key (process.env) — no proxy, no browser
|
|
3
3
|
// exposure. Used by the always-on autopilot engine.
|
|
4
4
|
|
|
5
|
-
const REQUEST_TIMEOUT_MS =
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
6
|
+
const MAX_ATTEMPTS = 2;
|
|
7
|
+
const BACKOFF_MS = [800]; // backoff before the single retry (rate-limit / transient failures)
|
|
6
8
|
|
|
7
9
|
/** Pull the model's JSON out of an OpenAI-shaped chat-completion response. */
|
|
8
10
|
export function extractJsonContent(data: unknown): unknown {
|
|
@@ -25,6 +27,8 @@ export function isLiteLLMConfigured(): boolean {
|
|
|
25
27
|
*/
|
|
26
28
|
export async function litellmJson<T>(opts: {
|
|
27
29
|
maxTokens?: number;
|
|
30
|
+
/** Override the model for this call (e.g. the autopilot engine pins a non-reasoning model). */
|
|
31
|
+
model?: string;
|
|
28
32
|
systemInstruction: string;
|
|
29
33
|
userPrompt: string;
|
|
30
34
|
}): Promise<null | T> {
|
|
@@ -32,34 +36,20 @@ export async function litellmJson<T>(opts: {
|
|
|
32
36
|
if (!cfg) {
|
|
33
37
|
return null;
|
|
34
38
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
],
|
|
47
|
-
model: cfg.model,
|
|
48
|
-
}),
|
|
49
|
-
headers: { Authorization: `Bearer ${cfg.key}`, 'Content-Type': 'application/json' },
|
|
50
|
-
method: 'POST',
|
|
51
|
-
signal: controller.signal,
|
|
52
|
-
});
|
|
53
|
-
if (!res.ok) {
|
|
54
|
-
return null;
|
|
39
|
+
// Attempt with JSON mode (forces the model to emit ONLY valid JSON — verbose/reasoning models
|
|
40
|
+
// otherwise wrap prose around it), retrying with backoff for rate-limit / transient burst
|
|
41
|
+
// failures. If every JSON-mode attempt fails, fall back ONCE without response_format in case the
|
|
42
|
+
// gateway rejects it — so a non-supporting backend degrades to prior behavior instead of breaking.
|
|
43
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
44
|
+
const result = await attemptCompletion<T>(cfg, opts, true);
|
|
45
|
+
if (result !== null) {
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
if (attempt < MAX_ATTEMPTS - 1) {
|
|
49
|
+
await delay(BACKOFF_MS[attempt]);
|
|
55
50
|
}
|
|
56
|
-
const data: unknown = await res.json();
|
|
57
|
-
return extractJsonContent(data) as null | T;
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
} finally {
|
|
61
|
-
clearTimeout(timer);
|
|
62
51
|
}
|
|
52
|
+
return attemptCompletion<T>(cfg, opts, false);
|
|
63
53
|
}
|
|
64
54
|
|
|
65
55
|
/**
|
|
@@ -79,28 +69,94 @@ export function parseJsonResponse(raw: string): unknown {
|
|
|
79
69
|
try {
|
|
80
70
|
return JSON.parse(cleaned);
|
|
81
71
|
} catch {
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
// Scan for the FIRST balanced {...} region that actually parses. A reasoning model often emits
|
|
73
|
+
// an invalid brace region (e.g. "{not: valid}") in its preamble BEFORE the real JSON, so on a
|
|
74
|
+
// failed candidate we must advance to the next "{" and keep scanning — not give up (the old
|
|
75
|
+
// `return null` here discarded every later region and collapsed consensus to empty).
|
|
76
|
+
let start = cleaned.indexOf('{');
|
|
77
|
+
while (start !== -1) {
|
|
84
78
|
let depth = 0;
|
|
79
|
+
let end = -1;
|
|
85
80
|
for (let i = start; i < cleaned.length; i++) {
|
|
86
81
|
if (cleaned[i] === '{') {
|
|
87
82
|
depth++;
|
|
88
83
|
} else if (cleaned[i] === '}') {
|
|
89
84
|
depth--;
|
|
90
85
|
if (depth === 0) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
} catch {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
86
|
+
end = i;
|
|
87
|
+
break;
|
|
96
88
|
}
|
|
97
89
|
}
|
|
98
90
|
}
|
|
91
|
+
if (end === -1) {
|
|
92
|
+
return null; // unbalanced through end of string — nothing more to try
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(cleaned.slice(start, end + 1));
|
|
96
|
+
} catch {
|
|
97
|
+
start = cleaned.indexOf('{', start + 1);
|
|
98
|
+
}
|
|
99
99
|
}
|
|
100
100
|
return null;
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
async function attemptCompletion<T>(
|
|
105
|
+
cfg: { base: string; key: string; model: string },
|
|
106
|
+
opts: { maxTokens?: number; model?: string; systemInstruction: string; userPrompt: string },
|
|
107
|
+
jsonMode: boolean
|
|
108
|
+
): Promise<null | T> {
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
controller.abort();
|
|
112
|
+
}, REQUEST_TIMEOUT_MS);
|
|
113
|
+
try {
|
|
114
|
+
const body: Record<string, unknown> = {
|
|
115
|
+
max_tokens: opts.maxTokens ?? 400,
|
|
116
|
+
messages: [
|
|
117
|
+
{ content: opts.systemInstruction, role: 'system' },
|
|
118
|
+
{ content: opts.userPrompt, role: 'user' },
|
|
119
|
+
],
|
|
120
|
+
model: opts.model?.trim() || cfg.model,
|
|
121
|
+
temperature: 0,
|
|
122
|
+
};
|
|
123
|
+
if (jsonMode) {
|
|
124
|
+
body.response_format = { type: 'json_object' };
|
|
125
|
+
}
|
|
126
|
+
const res = await fetch(`${cfg.base}/chat/completions`, {
|
|
127
|
+
body: JSON.stringify(body),
|
|
128
|
+
headers: { Authorization: `Bearer ${cfg.key}`, 'Content-Type': 'application/json' },
|
|
129
|
+
method: 'POST',
|
|
130
|
+
signal: controller.signal,
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
// Surface the failure — silently returning null here made the whole autopilot pipeline
|
|
134
|
+
// produce empty consensus invisibly (no log, no signal). 429 = the LiteLLM key's
|
|
135
|
+
// max_parallel_requests is exhausted (often by other processes sharing the key).
|
|
136
|
+
const detail = await res.text().catch(() => '');
|
|
137
|
+
console.warn(
|
|
138
|
+
`[litellm] HTTP ${res.status} (model=${body.model as string}, jsonMode=${jsonMode}): ${detail.slice(0, 200)}`
|
|
139
|
+
);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const data: unknown = await res.json();
|
|
143
|
+
return extractJsonContent(data) as null | T;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
146
|
+
// AbortError = our REQUEST_TIMEOUT_MS fired (the gateway hung). Anything else = network/parse.
|
|
147
|
+
console.warn(`[litellm] request failed (model=${opts.model?.trim() || cfg.model}): ${reason}`);
|
|
148
|
+
return null;
|
|
149
|
+
} finally {
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function delay(ms: number): Promise<void> {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
setTimeout(resolve, ms);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
104
160
|
function litellmConfig(): null | { base: string; key: string; model: string } {
|
|
105
161
|
const base = process.env.LITELLM_BASE_URL?.trim();
|
|
106
162
|
const key = process.env.LITELLM_API_KEY?.trim();
|
|
@@ -60,7 +60,14 @@ export function mergeNextStepConsensus(
|
|
|
60
60
|
// Step 3: find an existing cluster to join.
|
|
61
61
|
const targetCluster = findOrCreateCluster(clusters, norm);
|
|
62
62
|
targetCluster.agentIndices.add(agentIndex);
|
|
63
|
-
|
|
63
|
+
// Sanitise confidence at ingestion: the type says `number` but the value comes from raw LLM
|
|
64
|
+
// JSON, so a missing/NaN/Infinity/out-of-range confidence must NOT leak into meanConf (it
|
|
65
|
+
// would poison the mean and could spuriously clear the downstream 0.7 inject floor). Invalid
|
|
66
|
+
// → 0 (correctly fails the floor); valid → clamped to [0,1].
|
|
67
|
+
targetCluster.members.push({
|
|
68
|
+
confidence: safeConfidence(proposal.confidence),
|
|
69
|
+
text: proposal.text,
|
|
70
|
+
});
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
@@ -175,11 +182,29 @@ function normalize(text: string): string {
|
|
|
175
182
|
.replace(/[.,;:!?]+$/, '');
|
|
176
183
|
}
|
|
177
184
|
|
|
185
|
+
/** Coerce a raw (untrusted) confidence into a finite number in [0,1]; invalid → 0. */
|
|
186
|
+
function safeConfidence(value: unknown): number {
|
|
187
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
return Math.max(0, Math.min(1, value));
|
|
191
|
+
}
|
|
192
|
+
|
|
178
193
|
// ── Private helper ──────────────────────────────────────────────────────────
|
|
179
194
|
|
|
180
195
|
/**
|
|
181
196
|
* Tokenize a normalized string into a set of tokens.
|
|
182
197
|
*/
|
|
183
198
|
function tokenSet(normalized: string): Set<string> {
|
|
184
|
-
|
|
199
|
+
// Strip wrapping punctuation from each token so code-bearing proposals cluster: the lenses phrase
|
|
200
|
+
// the same action differently and wrap code in backticks/quotes (`return a - b`, "calc.js"), which
|
|
201
|
+
// otherwise fragments the tokens ("`return" ≠ "return") and makes the Jaccard overlap undercount
|
|
202
|
+
// real agreement. Pure-punctuation tokens (operators like - / +) drop out, which is fine — we are
|
|
203
|
+
// grouping intent, not preserving exact syntax.
|
|
204
|
+
return new Set(
|
|
205
|
+
normalized
|
|
206
|
+
.split(/\s+/)
|
|
207
|
+
.map((t) => t.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, ''))
|
|
208
|
+
.filter((t) => t.length > 0)
|
|
209
|
+
);
|
|
185
210
|
}
|
|
@@ -14,9 +14,11 @@ import Database from 'better-sqlite3';
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
|
|
17
|
+
import { shooterDataDir } from '../utils/shooter-home.js';
|
|
18
|
+
|
|
17
19
|
// ── Constants ────────────────────────────────────────────────────────
|
|
18
20
|
|
|
19
|
-
const DB_DIR =
|
|
21
|
+
const DB_DIR = shooterDataDir();
|
|
20
22
|
const DB_PATH = path.join(DB_DIR, 'shooter.db');
|
|
21
23
|
|
|
22
24
|
// ── Column list ──────────────────────────────────────────────────────
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Inject a default `--permission-mode` into a claude launch, configurable via the
|
|
2
|
+
// SHOOTER_AGENT_PERMISSION_MODE env var, so managed agent terminals can actually act instead of
|
|
3
|
+
// being auto-denied by a restrictive global Claude config. Claude-only (the flag is
|
|
4
|
+
// claude-specific) and never overrides an explicit --permission-mode the caller passed.
|
|
5
|
+
// Pure — unit-tested in tests/agent-launch.test.cjs.
|
|
6
|
+
|
|
7
|
+
const CLAUDE_COMMANDS = new Set(['claude']);
|
|
8
|
+
|
|
9
|
+
export function withAgentPermissionMode(
|
|
10
|
+
command: string,
|
|
11
|
+
args: string[],
|
|
12
|
+
mode: string | undefined
|
|
13
|
+
): string[] {
|
|
14
|
+
const trimmed = (mode ?? '').trim();
|
|
15
|
+
if (trimmed.length === 0) {
|
|
16
|
+
return args; // feature off when unset
|
|
17
|
+
}
|
|
18
|
+
const base = command.split('/').pop() ?? command;
|
|
19
|
+
if (!CLAUDE_COMMANDS.has(base)) {
|
|
20
|
+
return args; // --permission-mode is claude-specific
|
|
21
|
+
}
|
|
22
|
+
if (args.includes('--permission-mode')) {
|
|
23
|
+
return args; // an explicit flag always wins
|
|
24
|
+
}
|
|
25
|
+
return [...args, '--permission-mode', trimmed];
|
|
26
|
+
}
|
|
@@ -114,6 +114,12 @@ try {
|
|
|
114
114
|
/* best effort */
|
|
115
115
|
}
|
|
116
116
|
ptyEnv.SHOOTER_CLIPBOARD_DIR = clipboardDir;
|
|
117
|
+
// Tag this PTY with its Shooter terminal id so in-agent lifecycle hooks
|
|
118
|
+
// (notifier.cjs) can attribute their events to THIS managed terminal. The
|
|
119
|
+
// autopilot engine + phone driver key on terminalId; a plain shell that a
|
|
120
|
+
// user starts outside Shooter never sees this var, so its behaviour is
|
|
121
|
+
// unchanged.
|
|
122
|
+
ptyEnv.SHOOTER_TERMINAL_ID = id;
|
|
117
123
|
|
|
118
124
|
// Prepend clipboard shim scripts to PATH so tools find our xclip/wl-paste
|
|
119
125
|
const shimsDir = path.resolve(__dirname, '..', 'scripts', 'clipboard-shims');
|