pi-mega-compact 0.13.2 → 0.13.4

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.
Files changed (55) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +13 -0
  2. package/dist/extensions/dashboard-server/api-contracts/rag-settings.js +10 -0
  3. package/dist/extensions/dashboard-server/routes-rag-settings.js +169 -0
  4. package/dist/extensions/dashboard-server/routes.js +1 -0
  5. package/dist/extensions/dashboard-server/server.js +3 -1
  6. package/dist/src/config.js +14 -7
  7. package/dist/src/httpEmbedder.js +14 -2
  8. package/dist/src/recall/sync.js +4 -3
  9. package/extensions/dashboard-client/dist/assets/{CacheTab-DPRHjYZ0.js → CacheTab-Cqnc7lMg.js} +2 -2
  10. package/extensions/dashboard-client/dist/assets/{CacheTab-DPRHjYZ0.js.map → CacheTab-Cqnc7lMg.js.map} +1 -1
  11. package/extensions/dashboard-client/dist/assets/{EventsTab-CgwYq-Qf.js → EventsTab-tYk3VFAp.js} +2 -2
  12. package/extensions/dashboard-client/dist/assets/{EventsTab-CgwYq-Qf.js.map → EventsTab-tYk3VFAp.js.map} +1 -1
  13. package/extensions/dashboard-client/dist/assets/{HealthTab-BgOCjVzR.js → HealthTab-D2_kjgxw.js} +2 -2
  14. package/extensions/dashboard-client/dist/assets/{HealthTab-BgOCjVzR.js.map → HealthTab-D2_kjgxw.js.map} +1 -1
  15. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-Yw1yFpsy.js → MaintenanceTab-Bc-uPYrz.js} +2 -2
  16. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-Yw1yFpsy.js.map → MaintenanceTab-Bc-uPYrz.js.map} +1 -1
  17. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-yHUX7Yzi.js → MemoryMapTab-D1V9g_NC.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-yHUX7Yzi.js.map → MemoryMapTab-D1V9g_NC.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{MetricsTab-CcH9fP8P.js → MetricsTab-DT_XfL96.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{MetricsTab-CcH9fP8P.js.map → MetricsTab-DT_XfL96.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{OverviewTab-BOs8ZNev.js → OverviewTab-BY6KZCme.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{OverviewTab-BOs8ZNev.js.map → OverviewTab-BY6KZCme.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{ReposTab-B-MfSPvg.js → ReposTab-CeS-tpDM.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{ReposTab-B-MfSPvg.js.map → ReposTab-CeS-tpDM.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{SessionsTab-BmeN649Q.js → SessionsTab-C30AwyoF.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{SessionsTab-BmeN649Q.js.map → SessionsTab-C30AwyoF.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js +2 -0
  28. package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js.map +1 -0
  29. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DnPg0L0z.js → TimeSavedCard-p55K1tFP.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DnPg0L0z.js.map → TimeSavedCard-p55K1tFP.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{TopicsTab-BODHOg_b.js → TopicsTab-D8FqyAbK.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{TopicsTab-BODHOg_b.js.map → TopicsTab-D8FqyAbK.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{TurnsTab-BK0I7hKr.js → TurnsTab-ju-nXDys.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{TurnsTab-BK0I7hKr.js.map → TurnsTab-ju-nXDys.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{index-mOlljTFI.js → index-BEj1Zahy.js} +8 -8
  36. package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js.map +1 -0
  37. package/extensions/dashboard-client/dist/assets/{useSSE-BYOtv1-3.js → useSSE-p1pOI_-P.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{useSSE-BYOtv1-3.js.map → useSSE-p1pOI_-P.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/index.html +1 -1
  40. package/extensions/dashboard-client/src/api/client.ts +17 -0
  41. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +2 -0
  42. package/extensions/dashboard-client/src/tabs/SetupTab/RagSettingsCard.tsx +160 -0
  43. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +23 -0
  44. package/extensions/dashboard-server/api-contracts/index.ts +8 -0
  45. package/extensions/dashboard-server/api-contracts/rag-settings.ts +42 -0
  46. package/extensions/dashboard-server/routes-rag-settings.ts +207 -0
  47. package/extensions/dashboard-server/routes.ts +1 -0
  48. package/extensions/dashboard-server/server.ts +2 -0
  49. package/package.json +1 -1
  50. package/src/config.ts +15 -7
  51. package/src/httpEmbedder.ts +14 -2
  52. package/src/recall/sync.ts +4 -3
  53. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +0 -2
  54. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +0 -1
  55. package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{r as e}from"./index-mOlljTFI.js";function R(i={}){const{maxEvents:c=500,maxReconnects:l=5}=i,[f,v]=e.useState([]),[E,n]=e.useState("connecting"),[S,p]=e.useState(0),[m,d]=e.useState(null),o=e.useRef(0),s=e.useRef(null),r=e.useCallback(()=>{s.current&&s.current.close(),n("connecting");const t=new EventSource("/api/events");s.current=t,t.onopen=()=>{n("connected"),o.current=0},t.onmessage=g=>{try{const x=JSON.parse(g.data);v(u=>{const a=[...u,x];return a.length>c?a.slice(-c):a}),p(u=>u+1),d(Date.now())}catch{}},t.onerror=()=>{t.close(),o.current<l?(o.current+=1,n("connecting"),setTimeout(r,1e3)):n("disconnected")}},[c,l]);return e.useEffect(()=>(r(),()=>{s.current?.close()}),[r]),{events:f,status:E,eventCount:S,lastEventAt:m}}export{R as u};
2
- //# sourceMappingURL=useSSE-BYOtv1-3.js.map
1
+ import{r as e}from"./index-BEj1Zahy.js";function R(i={}){const{maxEvents:c=500,maxReconnects:l=5}=i,[f,v]=e.useState([]),[E,n]=e.useState("connecting"),[S,p]=e.useState(0),[m,d]=e.useState(null),o=e.useRef(0),s=e.useRef(null),r=e.useCallback(()=>{s.current&&s.current.close(),n("connecting");const t=new EventSource("/api/events");s.current=t,t.onopen=()=>{n("connected"),o.current=0},t.onmessage=g=>{try{const x=JSON.parse(g.data);v(u=>{const a=[...u,x];return a.length>c?a.slice(-c):a}),p(u=>u+1),d(Date.now())}catch{}},t.onerror=()=>{t.close(),o.current<l?(o.current+=1,n("connecting"),setTimeout(r,1e3)):n("disconnected")}},[c,l]);return e.useEffect(()=>(r(),()=>{s.current?.close()}),[r]),{events:f,status:E,eventCount:S,lastEventAt:m}}export{R as u};
2
+ //# sourceMappingURL=useSSE-p1pOI_-P.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSSE-BYOtv1-3.js","sources":["../../src/hooks/useSSE.ts"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect"],"mappings":"wCA6BO,SAASA,EAAOC,EAAyB,GAAkB,CAChE,KAAM,CAAE,UAAAC,EAAY,IAAK,cAAAC,EAAgB,GAAMF,EACzC,CAACG,EAAQC,CAAS,EAAIC,EAAAA,SAAqB,CAAA,CAAE,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAoB,YAAY,EACtD,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,CAAC,EACxC,CAACK,EAAaC,CAAc,EAAIN,EAAAA,SAAwB,IAAI,EAC5DO,EAAiBC,EAAAA,OAAO,CAAC,EACzBC,EAAYD,EAAAA,OAA2B,IAAI,EAE3CE,EAAUC,EAAAA,YAAY,IAAM,CAC5BF,EAAU,SACZA,EAAU,QAAQ,MAAA,EAEpBP,EAAU,YAAY,EACtB,MAAMU,EAAS,IAAI,YAAY,aAAa,EAC5CH,EAAU,QAAUG,EAEpBA,EAAO,OAAS,IAAM,CACpBV,EAAU,WAAW,EACrBK,EAAe,QAAU,CAC3B,EAEAK,EAAO,UAAaC,GAAU,CAC5B,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,EAAM,IAAI,EACpCd,EAAUgB,GAAQ,CAChB,MAAMC,EAAO,CAAC,GAAGD,EAAMD,CAAM,EAC7B,OAAOE,EAAK,OAASpB,EAAYoB,EAAK,MAAM,CAACpB,CAAS,EAAIoB,CAC5D,CAAC,EACDZ,EAAca,GAAKA,EAAI,CAAC,EACxBX,EAAe,KAAK,KAAK,CAC3B,MAAQ,CAER,CACF,EAEAM,EAAO,QAAU,IAAM,CACrBA,EAAO,MAAA,EACHL,EAAe,QAAUV,GAC3BU,EAAe,SAAW,EAC1BL,EAAU,YAAY,EAEtB,WAAWQ,EAAS,GAAI,GAExBR,EAAU,cAAc,CAE5B,CACF,EAAG,CAACN,EAAWC,CAAa,CAAC,EAE7BqB,OAAAA,EAAAA,UAAU,KACRR,EAAA,EACO,IAAM,CACXD,EAAU,SAAS,MAAA,CACrB,GACC,CAACC,CAAO,CAAC,EAEL,CAAE,OAAAZ,EAAQ,OAAAG,EAAQ,WAAAE,EAAY,YAAAE,CAAA,CACvC"}
1
+ {"version":3,"file":"useSSE-p1pOI_-P.js","sources":["../../src/hooks/useSSE.ts"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect"],"mappings":"wCA6BO,SAASA,EAAOC,EAAyB,GAAkB,CAChE,KAAM,CAAE,UAAAC,EAAY,IAAK,cAAAC,EAAgB,GAAMF,EACzC,CAACG,EAAQC,CAAS,EAAIC,EAAAA,SAAqB,CAAA,CAAE,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAoB,YAAY,EACtD,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,CAAC,EACxC,CAACK,EAAaC,CAAc,EAAIN,EAAAA,SAAwB,IAAI,EAC5DO,EAAiBC,EAAAA,OAAO,CAAC,EACzBC,EAAYD,EAAAA,OAA2B,IAAI,EAE3CE,EAAUC,EAAAA,YAAY,IAAM,CAC5BF,EAAU,SACZA,EAAU,QAAQ,MAAA,EAEpBP,EAAU,YAAY,EACtB,MAAMU,EAAS,IAAI,YAAY,aAAa,EAC5CH,EAAU,QAAUG,EAEpBA,EAAO,OAAS,IAAM,CACpBV,EAAU,WAAW,EACrBK,EAAe,QAAU,CAC3B,EAEAK,EAAO,UAAaC,GAAU,CAC5B,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,EAAM,IAAI,EACpCd,EAAUgB,GAAQ,CAChB,MAAMC,EAAO,CAAC,GAAGD,EAAMD,CAAM,EAC7B,OAAOE,EAAK,OAASpB,EAAYoB,EAAK,MAAM,CAACpB,CAAS,EAAIoB,CAC5D,CAAC,EACDZ,EAAca,GAAKA,EAAI,CAAC,EACxBX,EAAe,KAAK,KAAK,CAC3B,MAAQ,CAER,CACF,EAEAM,EAAO,QAAU,IAAM,CACrBA,EAAO,MAAA,EACHL,EAAe,QAAUV,GAC3BU,EAAe,SAAW,EAC1BL,EAAU,YAAY,EAEtB,WAAWQ,EAAS,GAAI,GAExBR,EAAU,cAAc,CAE5B,CACF,EAAG,CAACN,EAAWC,CAAa,CAAC,EAE7BqB,OAAAA,EAAAA,UAAU,KACRR,EAAA,EACO,IAAM,CACXD,EAAU,SAAS,MAAA,CACrB,GACC,CAACC,CAAO,CAAC,EAEL,CAAE,OAAAZ,EAAQ,OAAAG,EAAQ,WAAAE,EAAY,YAAAE,CAAA,CACvC"}
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>mega-compact dashboard</title>
7
- <script type="module" crossorigin src="./assets/index-mOlljTFI.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-BEj1Zahy.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/index-CUNn22iV.css">
9
9
  </head>
10
10
  <body>
@@ -48,6 +48,9 @@ import type {
48
48
  SetupConfigureResponse,
49
49
  EmbedderHealthResponse,
50
50
  RaptorTreeResponse,
51
+ RagSettingsResponse,
52
+ RagSettingsRequest,
53
+ RagSettingsResponsePost,
51
54
  } from "@contracts";
52
55
 
53
56
  /** Error thrown when a dashboard API response is not 2xx. */
@@ -323,3 +326,17 @@ export function fetchRaptorTree(sessionId?: string): Promise<RaptorTreeResponse>
323
326
  export function fetchEmbedderHealth(): Promise<EmbedderHealthResponse> {
324
327
  return getJson<EmbedderHealthResponse>(ENDPOINTS.embedderHealth.path);
325
328
  }
329
+
330
+ // ─── RAG Settings ─────────────────────────────────────────────────────
331
+
332
+ /** GET /api/rag-settings — read the current state of all RAG feature flags. */
333
+ export function fetchRagSettings(): Promise<RagSettingsResponse> {
334
+ return getJson<RagSettingsResponse>(ENDPOINTS.ragSettings.path);
335
+ }
336
+
337
+ /** POST /api/rag-settings — toggle RAG feature flags (writes _DISABLED env vars). */
338
+ export function postRagSettings(
339
+ body: RagSettingsRequest,
340
+ ): Promise<RagSettingsResponsePost> {
341
+ return postJson<RagSettingsResponsePost>(ENDPOINTS.ragSettingsUpdate.path, body);
342
+ }
@@ -13,6 +13,7 @@ import { useState, useEffect, useCallback } from "react";
13
13
  import type { SetupStatusResponse, SetupDetectResponse, SetupConfigureResponse } from "@contracts";
14
14
  import { fetchSetupStatus, fetchSetupDetect, configureEmbedder } from "../../api/client";
15
15
  import EmbedderHealthCard from "./EmbedderHealthCard";
16
+ import RagSettingsCard from "./RagSettingsCard";
16
17
 
17
18
  const styles: Record<string, React.CSSProperties> = {
18
19
  container: {
@@ -463,6 +464,7 @@ export default function EmbedderSetup(): React.ReactElement {
463
464
  </li>
464
465
  </ul>
465
466
  </div>
467
+ <RagSettingsCard />
466
468
  </div>
467
469
  );
468
470
  }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * SetupTab/RagSettingsCard.tsx — RAG feature flag toggles (S57 B1–B5).
3
+ *
4
+ * Shows each RAG flag as a checkbox toggle. Reads/writes the .mega-compact.env
5
+ * file via the /api/rag-settings endpoint. HyDE (B5) is greyed out when no
6
+ * LLM embedder (HttpEmbedder) is active — it requires an LLM to function.
7
+ *
8
+ * PREVENT-PI-004: all data comes from localhost dashboard API endpoints.
9
+ */
10
+ import type React from "react";
11
+ import { useState, useEffect, useCallback } from "react";
12
+ import type { RagSettingsResponse } from "@contracts";
13
+ import { fetchRagSettings, postRagSettings } from "../../api/client";
14
+
15
+ const styles: Record<string, React.CSSProperties> = {
16
+ section: {
17
+ background: "#1a1a2e",
18
+ borderRadius: "8px",
19
+ padding: "1rem",
20
+ marginBottom: "1rem",
21
+ border: "1px solid #2a2a4e",
22
+ },
23
+ sectionTitle: {
24
+ fontSize: "1.1rem",
25
+ fontWeight: 700,
26
+ marginTop: 0,
27
+ marginBottom: "0.75rem",
28
+ color: "#e0e0e0",
29
+ borderBottom: "1px solid #333",
30
+ paddingBottom: "0.5rem",
31
+ },
32
+ flagRow: {
33
+ display: "flex",
34
+ alignItems: "flex-start",
35
+ gap: "0.5rem",
36
+ padding: "0.5rem 0",
37
+ borderBottom: "1px solid #2a2a4e",
38
+ },
39
+ checkbox: {
40
+ marginTop: "0.15rem",
41
+ cursor: "pointer",
42
+ },
43
+ label: {
44
+ fontWeight: 600,
45
+ color: "#e0e0e0",
46
+ fontSize: "0.9rem",
47
+ },
48
+ description: {
49
+ fontSize: "0.8rem",
50
+ color: "#a0a0c0",
51
+ marginTop: "0.15rem",
52
+ },
53
+ hint: {
54
+ fontSize: "0.75rem",
55
+ color: "#ff9800",
56
+ marginTop: "0.15rem",
57
+ },
58
+ banner: {
59
+ background: "#3a2a00",
60
+ border: "1px solid #665500",
61
+ borderRadius: "6px",
62
+ padding: "0.5rem 0.75rem",
63
+ marginTop: "0.5rem",
64
+ color: "#ffcc00",
65
+ fontSize: "0.8rem",
66
+ },
67
+ };
68
+
69
+ export default function RagSettingsCard(): React.ReactElement {
70
+ const [settings, setSettings] = useState<RagSettingsResponse | null>(null);
71
+ const [error, setError] = useState<string | null>(null);
72
+ const [saving, setSaving] = useState(false);
73
+ const [restartBanner, setRestartBanner] = useState(false);
74
+
75
+ const load = useCallback(() => {
76
+ fetchRagSettings()
77
+ .then(setSettings)
78
+ .catch((e: unknown) =>
79
+ setError(e instanceof Error ? e.message : String(e)),
80
+ );
81
+ }, []);
82
+
83
+ useEffect(() => {
84
+ load();
85
+ }, [load]);
86
+
87
+ useEffect(() => {
88
+ const id = setInterval(load, 30000);
89
+ return () => clearInterval(id);
90
+ }, [load]);
91
+
92
+ const toggle = useCallback(
93
+ (key: string, enabled: boolean) => {
94
+ if (!settings) return;
95
+ const flag = settings.flags.find((f) => f.key === key);
96
+ if (!flag || (flag.requiresLlm && !settings.llmActive)) return;
97
+ setSaving(true);
98
+ postRagSettings({ flags: { [key]: enabled } })
99
+ .then(() => {
100
+ setRestartBanner(true);
101
+ setSaving(false);
102
+ load();
103
+ })
104
+ .catch((e: unknown) => {
105
+ setError(e instanceof Error ? e.message : String(e));
106
+ setSaving(false);
107
+ });
108
+ },
109
+ [settings, load],
110
+ );
111
+
112
+ return (
113
+ <div style={styles.section}>
114
+ <h3 style={styles.sectionTitle}>RAG Settings</h3>
115
+ <p style={{ fontSize: "0.8rem", color: "#a0a0c0", marginTop: 0 }}>
116
+ Recall pipeline features. All default ON — toggle off to disable.
117
+ Changes take effect after restart.
118
+ </p>
119
+ {error && (
120
+ <p style={{ color: "#f44336", fontSize: "0.85rem" }}>Error: {error}</p>
121
+ )}
122
+ {settings?.flags.map((flag) => {
123
+ const locked = flag.requiresLlm && !settings.llmActive;
124
+ return (
125
+ <div key={flag.key} style={styles.flagRow}>
126
+ <input
127
+ type="checkbox"
128
+ style={{
129
+ ...styles.checkbox,
130
+ opacity: locked ? 0.4 : 1,
131
+ cursor: locked ? "not-allowed" : "pointer",
132
+ }}
133
+ checked={flag.enabled && !locked}
134
+ disabled={locked || saving}
135
+ onChange={(e) => toggle(flag.key, e.currentTarget.checked)}
136
+ />
137
+ <div>
138
+ <div style={styles.label}>{flag.label}</div>
139
+ <div style={styles.description}>{flag.description}</div>
140
+ {locked && (
141
+ <div style={styles.hint}>
142
+ Requires an LLM embedder (Ollama/HTTP). Configure one in
143
+ the Embedder section above first.
144
+ </div>
145
+ )}
146
+ </div>
147
+ </div>
148
+ );
149
+ })}
150
+ {!settings && !error && (
151
+ <p style={{ color: "#888", fontSize: "0.85rem" }}>Loading...</p>
152
+ )}
153
+ {restartBanner && (
154
+ <div style={styles.banner}>
155
+ Settings saved. <strong>Restart pi</strong> to apply changes.
156
+ </div>
157
+ )}
158
+ </div>
159
+ );
160
+ }
@@ -19,6 +19,11 @@ import type { RaptorTreeResponse } from "../raptor.js";
19
19
  import type { MemoryStatusResponse } from "../memory.js";
20
20
  import type { SetupStatusResponse, SetupDetectResponse, SetupConfigureRequest, SetupConfigureResponse } from "../setup.js";
21
21
  import type { EmbedderHealthResponse } from "../embedder-health.js";
22
+ import type {
23
+ RagSettingsResponse,
24
+ RagSettingsRequest,
25
+ RagSettingsResponsePost,
26
+ } from "../rag-settings.js";
22
27
  import type {
23
28
  TurnsResponse,
24
29
  ConversationTurnsResponse,
@@ -357,4 +362,22 @@ export const ENDPOINTS = {
357
362
  description:
358
363
  "Probe the active embedder with a test embed and report status, latency, dimensions, and masked URL.",
359
364
  } as const satisfies EndpointDef<"GET", undefined, EmbedderHealthResponse>,
365
+
366
+ // ─── RAG Settings (Sprint B6) ─────────────────────────────────────
367
+
368
+ /** GET /api/rag-settings — Read the state of all RAG feature flags. */
369
+ ragSettings: {
370
+ method: "GET",
371
+ path: "/api/rag-settings",
372
+ description:
373
+ "Read the current state of all RAG feature flags (enabled/disabled).",
374
+ } as const satisfies EndpointDef<"GET", undefined, RagSettingsResponse>,
375
+
376
+ /** POST /api/rag-settings — Update RAG feature flag states. */
377
+ ragSettingsUpdate: {
378
+ method: "POST",
379
+ path: "/api/rag-settings",
380
+ description:
381
+ "Update RAG feature flag states by writing _DISABLED env vars to .mega-compact.env.",
382
+ } as const satisfies EndpointDef<"POST", RagSettingsRequest, RagSettingsResponsePost>,
360
383
  } as const;
@@ -232,3 +232,11 @@ export type {
232
232
  CacheHealthScore,
233
233
  CacheStripesResponse,
234
234
  } from "./cache-stripes.js";
235
+
236
+ // RAG Settings — feature flag toggles (B6)
237
+ export type {
238
+ RagFlagState,
239
+ RagSettingsResponse,
240
+ RagSettingsRequest,
241
+ RagSettingsResponsePost,
242
+ } from "./rag-settings.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * api-contracts/rag-settings.ts — RAG feature flag API response types.
3
+ *
4
+ * Types for the /api/rag-settings endpoint used by the dashboard RAG Settings
5
+ * panel to read and toggle the five RAG feature flags (B1–B5).
6
+ *
7
+ * PREVENT-PI-004: type definitions only, no network code.
8
+ * PREVENT-011: no `any` type — all types are explicit.
9
+ */
10
+
11
+ /**单个 RAG feature flag state for the dashboard. */
12
+ export interface RagFlagState {
13
+ /** Env var key without the _DISABLED suffix, e.g. "MEGACOMPACT_HYDE". */
14
+ readonly key: string;
15
+ /** Short human-readable label, e.g. "HyDE (Hypothetical Document Embeddings)". */
16
+ readonly label: string;
17
+ /** One-line description of what the flag does. */
18
+ readonly description: string;
19
+ /** Whether the flag is currently enabled (true = ON, false = disabled via _DISABLED). */
20
+ readonly enabled: boolean;
21
+ /** Whether this flag requires an LLM embedder (HttpEmbedder) to function. */
22
+ readonly requiresLlm: boolean;
23
+ }
24
+
25
+ /** Response for GET /api/rag-settings. */
26
+ export interface RagSettingsResponse {
27
+ readonly flags: RagFlagState[];
28
+ /** Whether an HttpEmbedder (LLM) is currently active — gates HyDE toggle. */
29
+ readonly llmActive: boolean;
30
+ }
31
+
32
+ /** Request body for POST /api/rag-settings. */
33
+ export interface RagSettingsRequest {
34
+ /** Map of flag key (e.g. "MEGACOMPACT_HYDE") to desired enabled state. */
35
+ readonly flags: Record<string, boolean>;
36
+ }
37
+
38
+ /** Response for POST /api/rag-settings. */
39
+ export interface RagSettingsResponsePost {
40
+ readonly envPath: string;
41
+ readonly restartRequired: boolean;
42
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * dashboard-server/routes-rag-settings.ts — RAG Settings route handler.
3
+ *
4
+ * GET /api/rag-settings — Returns the state of all RAG feature flags (B1–B5).
5
+ * POST /api/rag-settings — Toggles flags by writing MEGACOMPACT_*_DISABLED
6
+ * lines to the per-repo .mega-compact.env file.
7
+ *
8
+ * Guardrails: PREVENT-PI-004 (loopback-only), PREVENT-001 (null-safe JSON),
9
+ * PREVENT-011 (no `any`). Each JSON write carries a guardrails-allow annotation.
10
+ */
11
+
12
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import type { IncomingMessage, ServerResponse } from "node:http";
15
+ import type { RouteContext } from "./routes-core.js";
16
+ import { detectCurrentEmbedder } from "./routes-setup.js";
17
+ import type {
18
+ RagFlagState,
19
+ RagSettingsResponse,
20
+ RagSettingsRequest,
21
+ RagSettingsResponsePost,
22
+ } from "./api-contracts/rag-settings.js";
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // RAG_FLAGS — the five feature flags surfaced by this panel.
26
+ // ---------------------------------------------------------------------------
27
+
28
+ const RAG_FLAGS: {
29
+ key: string;
30
+ label: string;
31
+ description: string;
32
+ requiresLlm: boolean;
33
+ }[] = [
34
+ {
35
+ key: "MEGACOMPACT_QUERY_REFORMULATION",
36
+ label: "Query Reformulation",
37
+ description:
38
+ "TF-IDF keyword expansion for vague recall queries (TrigramEmbedder path)",
39
+ requiresLlm: false,
40
+ },
41
+ {
42
+ key: "MEGACOMPACT_TIERED_ROUTER",
43
+ label: "Tiered Recall Router",
44
+ description:
45
+ "L0 cache → L1 FTS5 → L2 HNSW routing for faster recall",
46
+ requiresLlm: false,
47
+ },
48
+ {
49
+ key: "MEGACOMPACT_RECALL_METRICS",
50
+ label: "Recall Quality Metrics",
51
+ description:
52
+ "Precision/recall scoring and logging for recall evaluation",
53
+ requiresLlm: false,
54
+ },
55
+ {
56
+ key: "MEGACOMPACT_MEMORY_GRAPH",
57
+ label: "Memory Graph",
58
+ description:
59
+ "Dashboard-oriented memory graph traversal across sessions",
60
+ requiresLlm: false,
61
+ },
62
+ {
63
+ key: "MEGACOMPACT_HYDE",
64
+ label: "HyDE (Hypothetical Document Embeddings)",
65
+ description:
66
+ "Generate hypothetical answer via LLM, embed it, RRF-fuse with raw-query results",
67
+ requiresLlm: true,
68
+ },
69
+ ];
70
+
71
+ /** Strip any MEGACOMPACT_*_DISABLED assignment lines from env file content. */
72
+ const DISABLED_LINE = /^export\s+MEGACOMPACT_\w+_DISABLED=/;
73
+
74
+ function isDisabled(key: string): boolean {
75
+ const v = process.env[key + "_DISABLED"];
76
+ return v === "true" || v === "1";
77
+ }
78
+
79
+ function readJsonBody(
80
+ req: IncomingMessage,
81
+ cb: (
82
+ result:
83
+ | { ok: true; value: Record<string, unknown> }
84
+ | { ok: false; error: string },
85
+ ) => void,
86
+ ): void {
87
+ let body = "";
88
+ let tooBig = false;
89
+ req.on("data", (chunk: Buffer) => {
90
+ if (body.length > 65536) {
91
+ tooBig = true;
92
+ return;
93
+ }
94
+ body += chunk.toString();
95
+ });
96
+ req.on("end", () => {
97
+ if (tooBig) return cb({ ok: false, error: "body_too_large" });
98
+ try {
99
+ const v = body ? JSON.parse(body) : {}; // PREVENT-001: parsed value type-checked below
100
+ if (typeof v !== "object" || v === null || Array.isArray(v)) {
101
+ return cb({ ok: false, error: "invalid_object" });
102
+ }
103
+ cb({ ok: true, value: v as Record<string, unknown> });
104
+ } catch {
105
+ cb({ ok: false, error: "invalid_json" });
106
+ }
107
+ });
108
+ req.on("error", () => cb({ ok: false, error: "read_error" }));
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // handleRagSettings — "/api/rag-settings"
113
+ // ---------------------------------------------------------------------------
114
+
115
+ export function handleRagSettings(
116
+ req: IncomingMessage,
117
+ res: ServerResponse,
118
+ ctx: RouteContext,
119
+ ): boolean {
120
+ if (req.url !== "/api/rag-settings") return false;
121
+
122
+ if (req.method === "GET") {
123
+ const flags: RagFlagState[] = RAG_FLAGS.map((f) => ({
124
+ key: f.key,
125
+ label: f.label,
126
+ description: f.description,
127
+ enabled: !isDisabled(f.key),
128
+ requiresLlm: f.requiresLlm,
129
+ }));
130
+ const body: RagSettingsResponse = {
131
+ flags,
132
+ llmActive: detectCurrentEmbedder() === "http",
133
+ };
134
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
135
+ res.writeHead(200, { "Content-Type": "application/json" });
136
+ res.end(JSON.stringify(body));
137
+ return true;
138
+ }
139
+
140
+ if (req.method === "POST") {
141
+ readJsonBody(req, (parsed) => {
142
+ if (!parsed.ok) {
143
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
144
+ res.writeHead(400, { "Content-Type": "application/json" });
145
+ res.end(JSON.stringify({ error: parsed.error }));
146
+ return;
147
+ }
148
+ const body = parsed.value as unknown as RagSettingsRequest;
149
+ const desired = body.flags;
150
+ if (typeof desired !== "object" || desired === null || Array.isArray(desired)) {
151
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
152
+ res.writeHead(400, { "Content-Type": "application/json" });
153
+ res.end(JSON.stringify({ error: "invalid_flags" }));
154
+ return;
155
+ }
156
+ // Only accept known RAG flag keys.
157
+ const known = new Set(RAG_FLAGS.map((f) => f.key));
158
+ for (const key of Object.keys(desired)) {
159
+ if (!known.has(key)) {
160
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
161
+ res.writeHead(400, { "Content-Type": "application/json" });
162
+ res.end(JSON.stringify({ error: `unknown_flag: ${key}` }));
163
+ return;
164
+ }
165
+ }
166
+
167
+ const envPath = join(ctx.stateDir, ".mega-compact.env");
168
+ let lines: string[] = [];
169
+ if (existsSync(envPath)) {
170
+ const content = readFileSync(envPath, "utf-8");
171
+ lines = content.split("\n").filter((line) => !DISABLED_LINE.test(line));
172
+ }
173
+ for (const key of Object.keys(desired)) {
174
+ if (desired[key] === false) {
175
+ lines.push(`export ${key}_DISABLED="true"`);
176
+ }
177
+ }
178
+ if (lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
179
+ try {
180
+ mkdirSync(ctx.stateDir, { recursive: true });
181
+ writeFileSync(envPath, lines.join("\n"), "utf-8");
182
+ } catch (e) {
183
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
184
+ res.writeHead(500, { "Content-Type": "application/json" });
185
+ res.end(
186
+ JSON.stringify({
187
+ error: `write_failed: ${e instanceof Error ? e.message : String(e)}`,
188
+ }),
189
+ );
190
+ return;
191
+ }
192
+ const resp: RagSettingsResponsePost = {
193
+ envPath,
194
+ restartRequired: true,
195
+ };
196
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
197
+ res.writeHead(200, { "Content-Type": "application/json" });
198
+ res.end(JSON.stringify(resp));
199
+ });
200
+ return true;
201
+ }
202
+
203
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
204
+ res.writeHead(405, { "Content-Type": "application/json" });
205
+ res.end(JSON.stringify({ error: "method_not_allowed" }));
206
+ return true;
207
+ }
@@ -27,3 +27,4 @@ export { handleRaptorTree } from "./routes-raptor.js";
27
27
  export { handleCacheStripes } from "./routes-cache.js";
28
28
  export { handleContextHealth, handleCachePoison, handleHealthSettings } from "./routes-health.js";
29
29
  export { handleEmbedderHealth } from "./routes-embedder-health.js";
30
+ export { handleRagSettings } from "./routes-rag-settings.js";
@@ -49,6 +49,7 @@ import {
49
49
  handleCachePoison,
50
50
  handleHealthSettings,
51
51
  handleEmbedderHealth,
52
+ handleRagSettings,
52
53
  handleStatic,
53
54
  } from "./routes.js";
54
55
 
@@ -249,6 +250,7 @@ export async function launchDashboardServer(
249
250
  if (handleCachePoison(req, res, ctx)) return;
250
251
  if (handleHealthSettings(req, res, ctx)) return;
251
252
  if (handleEmbedderHealth(req, res, ctx)) return;
253
+ if (handleRagSettings(req, res, ctx)) return;
252
254
  handleStatic(req, res, ctx);
253
255
  });
254
256
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-3-Clause",
package/src/config.ts CHANGED
@@ -104,7 +104,9 @@ export function memoryReviewCadence(band: PressureBand, baseInterval: number): n
104
104
  }
105
105
 
106
106
  // ---------------------------------------------------------------------------
107
- // S57 RAG Suite feature flags — each maps to MEGACOMPACT_* env var, default OFF
107
+ // S57 RAG Suite feature flags — all default ON with graceful fallback; opt OUT
108
+ // via MEGACOMPACT_<NAME>_DISABLED=true. Every feature degrades to existing
109
+ // behavior on error (non-fatal, best-effort).
108
110
  // ---------------------------------------------------------------------------
109
111
 
110
112
  function ragFlag(name: string): boolean {
@@ -113,22 +115,28 @@ function ragFlag(name: string): boolean {
113
115
  return v === "true" || v === "1";
114
116
  }
115
117
 
118
+ function ragEnabled(name: string): boolean {
119
+ return !ragFlag(name + "_DISABLED");
120
+ }
121
+
116
122
  /** B1: Query reformulation (keyword expansion via embedding neighbors). */
117
123
  export const RAG_QUERY_REFORMULATION = (): boolean =>
118
- ragFlag("MEGACOMPACT_QUERY_REFORMULATION");
124
+ ragEnabled("MEGACOMPACT_QUERY_REFORMULATION");
119
125
 
120
126
  /** B2: Tiered recall router (L0 cache → L1 FTS5 → L2 HNSW). */
121
127
  export const RAG_TIERED_ROUTER = (): boolean =>
122
- ragFlag("MEGACOMPACT_TIERED_ROUTER");
128
+ ragEnabled("MEGACOMPACT_TIERED_ROUTER");
123
129
 
124
130
  /** B3: Recall quality metrics (precision/recall scoring + logging). */
125
131
  export const RAG_RECALL_METRICS = (): boolean =>
126
- ragFlag("MEGACOMPACT_RECALL_METRICS");
132
+ ragEnabled("MEGACOMPACT_RECALL_METRICS");
127
133
 
128
134
  /** B4: Memory graph traversal (dashboard-oriented). */
129
135
  export const RAG_MEMORY_GRAPH = (): boolean =>
130
- ragFlag("MEGACOMPACT_MEMORY_GRAPH");
136
+ ragEnabled("MEGACOMPACT_MEMORY_GRAPH");
131
137
 
132
- /** B5: HyDE — generate a hypothetical answer doc via LLM (HttpEmbedder only). Default OFF. */
138
+ /** B5: HyDE — generate a hypothetical answer doc via LLM. Auto-ON when an
139
+ * HttpEmbedder is active (the LLM is configured for indexing); opt OUT with
140
+ * MEGACOMPACT_HYDE_DISABLED=true. TrigramEmbedder path is unaffected. */
133
141
  export const RAG_HYDE_ENABLED = (): boolean =>
134
- ragFlag("MEGACOMPACT_HYDE_ENABLED");
142
+ ragEnabled("MEGACOMPACT_HYDE");
@@ -173,10 +173,19 @@ export function embeddingConfigFromEnv(): HttpEmbedderOptions | null {
173
173
  };
174
174
  }
175
175
 
176
- /** Extract a single embedding vector from a tolerant OpenAI-style response. */
176
+ /** True if the URL is an Ollama /api/embeddings endpoint. */
177
+ function isOllamaEndpoint(url: string): boolean {
178
+ return url.includes("/api/embeddings");
179
+ }
180
+
181
+ /** Extract a single embedding vector from a tolerant response.
182
+ * Handles Ollama ({embedding:[...]}), OpenAI ({data:[{embedding:[...]}]}),
183
+ * and simple ({data:[...]} / {embeddings:[...]}) shapes. */
177
184
  function parseEmbedding(body: unknown): number[] {
178
185
  if (body && typeof body === "object") {
179
186
  const b = body as Record<string, unknown>;
187
+ // Ollama: { embedding: [0.1, ...] }
188
+ if (Array.isArray(b.embedding)) return b.embedding as number[];
180
189
  if (Array.isArray(b.data) && b.data[0] && typeof b.data[0] === "object") {
181
190
  const first = b.data[0] as Record<string, unknown>;
182
191
  if (Array.isArray(first.embedding)) return first.embedding as number[];
@@ -227,7 +236,10 @@ export class HttpEmbedder implements Embedder {
227
236
  }
228
237
 
229
238
  embed(text: string): Vector {
230
- const body = JSON.stringify({ input: [text] });
239
+ const ollama = isOllamaEndpoint(this.url);
240
+ const body = ollama
241
+ ? JSON.stringify({ model: process.env.MEGACOMPACT_OLLAMA_MODEL || "nomic-embed-text", prompt: text })
242
+ : JSON.stringify({ input: [text] });
231
243
  const headers: Record<string, string> = {
232
244
  "content-type": "application/json",
233
245
  ...this.headers,