pi-mega-compact 0.12.1 → 0.12.2

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/gather-debug.js +63 -0
  2. package/dist/extensions/dashboard-server/routes-setup.js +30 -4
  3. package/dist/src/memoryGraph/sources.js +42 -15
  4. package/dist/src/memoryGraph.js +17 -5
  5. package/dist/src/store/turns/schema.js +18 -2
  6. package/extensions/dashboard-client/dist/assets/{AchievementTiles-Cwg_Ab8R.js → AchievementTiles-Cntu89Xw.js} +2 -2
  7. package/extensions/dashboard-client/dist/assets/{AchievementTiles-Cwg_Ab8R.js.map → AchievementTiles-Cntu89Xw.js.map} +1 -1
  8. package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dk32-4Ha.js → AchievementsTab-4ckY9z4n.js} +2 -2
  9. package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dk32-4Ha.js.map → AchievementsTab-4ckY9z4n.js.map} +1 -1
  10. package/extensions/dashboard-client/dist/assets/{CacheTab-B9zR8UgD.js → CacheTab-BfGJkiaf.js} +2 -2
  11. package/extensions/dashboard-client/dist/assets/{CacheTab-B9zR8UgD.js.map → CacheTab-BfGJkiaf.js.map} +1 -1
  12. package/extensions/dashboard-client/dist/assets/{ConfigTab-DYRl3mJZ.js → ConfigTab-DV08rHoV.js} +2 -2
  13. package/extensions/dashboard-client/dist/assets/{ConfigTab-DYRl3mJZ.js.map → ConfigTab-DV08rHoV.js.map} +1 -1
  14. package/extensions/dashboard-client/dist/assets/{EventsTab-r_zXcrmZ.js → EventsTab-DHytZ0xs.js} +2 -2
  15. package/extensions/dashboard-client/dist/assets/{EventsTab-r_zXcrmZ.js.map → EventsTab-DHytZ0xs.js.map} +1 -1
  16. package/extensions/dashboard-client/dist/assets/{GameTab-DjwA_Q1v.js → GameTab-Ofpoz-6s.js} +2 -2
  17. package/extensions/dashboard-client/dist/assets/{GameTab-DjwA_Q1v.js.map → GameTab-Ofpoz-6s.js.map} +1 -1
  18. package/extensions/dashboard-client/dist/assets/{HealthTab-BjH73kQr.js → HealthTab-B0klAxD8.js} +2 -2
  19. package/extensions/dashboard-client/dist/assets/{HealthTab-BjH73kQr.js.map → HealthTab-B0klAxD8.js.map} +1 -1
  20. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DzeSq9vK.js → MaintenanceTab-CHKh3KPI.js} +2 -2
  21. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DzeSq9vK.js.map → MaintenanceTab-CHKh3KPI.js.map} +1 -1
  22. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BU9iM3ir.js → MemoryMapTab-B6FLQ-32.js} +2 -2
  23. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BU9iM3ir.js.map → MemoryMapTab-B6FLQ-32.js.map} +1 -1
  24. package/extensions/dashboard-client/dist/assets/{MetricsTab-lMVodOwY.js → MetricsTab-DvOjTDkW.js} +2 -2
  25. package/extensions/dashboard-client/dist/assets/{MetricsTab-lMVodOwY.js.map → MetricsTab-DvOjTDkW.js.map} +1 -1
  26. package/extensions/dashboard-client/dist/assets/{OverviewTab-D-laa6CW.js → OverviewTab-CqbONEwi.js} +2 -2
  27. package/extensions/dashboard-client/dist/assets/{OverviewTab-D-laa6CW.js.map → OverviewTab-CqbONEwi.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/{ReposTab-5Pn2cOOO.js → ReposTab-sc1GLn7i.js} +2 -2
  29. package/extensions/dashboard-client/dist/assets/{ReposTab-5Pn2cOOO.js.map → ReposTab-sc1GLn7i.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/{SessionsTab-Dtdmvl2C.js → SessionsTab-C5gB45oc.js} +2 -2
  31. package/extensions/dashboard-client/dist/assets/{SessionsTab-Dtdmvl2C.js.map → SessionsTab-C5gB45oc.js.map} +1 -1
  32. package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js +2 -0
  33. package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js.map +1 -0
  34. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B5Lw6fTp.js → TimeSavedCard-BBwvb4cC.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B5Lw6fTp.js.map → TimeSavedCard-BBwvb4cC.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{TopicsTab-Ds1IHhPw.js → TopicsTab-BsCf512_.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{TopicsTab-Ds1IHhPw.js.map → TopicsTab-BsCf512_.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/{TurnsTab-CG2RDRmh.js → TurnsTab-C3ti7NB5.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{TurnsTab-CG2RDRmh.js.map → TurnsTab-C3ti7NB5.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{index-C9dTXvDQ.js → index-BEeaI9uo.js} +3 -3
  41. package/extensions/dashboard-client/dist/assets/{index-C9dTXvDQ.js.map → index-BEeaI9uo.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{useSSE-B6l8O-yi.js → useSSE-BMSSO9wB.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{useSSE-B6l8O-yi.js.map → useSSE-BMSSO9wB.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/index.html +1 -1
  45. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +19 -0
  46. package/extensions/dashboard-server/api-contracts/maintenance.ts +1 -0
  47. package/extensions/dashboard-server/api-contracts/setup.ts +7 -1
  48. package/extensions/dashboard-server/gather-debug.ts +63 -0
  49. package/extensions/dashboard-server/routes-setup.ts +28 -4
  50. package/package.json +1 -1
  51. package/src/memoryGraph/sources.ts +49 -15
  52. package/src/memoryGraph.ts +17 -5
  53. package/src/store/turns/schema.ts +18 -2
  54. package/extensions/dashboard-client/dist/assets/SetupTab-rKGgWpI0.js +0 -2
  55. package/extensions/dashboard-client/dist/assets/SetupTab-rKGgWpI0.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{r as e}from"./index-C9dTXvDQ.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-B6l8O-yi.js.map
1
+ import{r as e}from"./index-BEeaI9uo.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-BMSSO9wB.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSSE-B6l8O-yi.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-BMSSO9wB.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-C9dTXvDQ.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-BEeaI9uo.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/index-CUNn22iV.css">
9
9
  </head>
10
10
  <body>
@@ -207,6 +207,25 @@ export default function SetupTab(): React.ReactElement {
207
207
  {embedderLabel(status.currentEmbedder)}
208
208
  </span>
209
209
  </div>
210
+ {"configuredEmbedder" in status && status.configuredEmbedder !== status.currentEmbedder && (
211
+ <div style={styles.warning}>
212
+ <strong>Configured but not active:</strong>{" "}
213
+ {embedderLabel(status.configuredEmbedder)} is configured in
214
+ .mega-compact.env but not yet loaded.{" "}
215
+ <strong>Restart pi</strong> to activate it.
216
+ {status.configuredUrl && (
217
+ <> ({status.configuredUrl})</>
218
+ )}
219
+ </div>
220
+ )}
221
+ {"restartRequired" in status && status.restartRequired && (
222
+ <div style={{ ...styles.row, color: "#ff9800" }}>
223
+ <span style={styles.label}>Status:</span>
224
+ <span style={styles.value}>
225
+ Restart required to activate the configured embedder
226
+ </span>
227
+ </div>
228
+ )}
210
229
  <div style={styles.row}>
211
230
  <span style={styles.label}>Embedding URL:</span>
212
231
  <span style={styles.value}>
@@ -94,4 +94,5 @@ export interface DebugBundleResponse {
94
94
  dashboardSnapshot: unknown | { error: string };
95
95
  recentEvents: unknown[];
96
96
  criticalEvents: unknown[];
97
+ dataSourceAudit?: Record<string, unknown> | { error: string };
97
98
  }
@@ -13,8 +13,14 @@
13
13
  * Returns the current embedder configuration read from the environment.
14
14
  */
15
15
  export interface SetupStatusResponse {
16
- /** Which embedder implementation is currently active. */
16
+ /** Which embedder implementation is currently active in the running process. */
17
17
  readonly currentEmbedder: "trigram" | "http" | "minilm" | "unknown";
18
+ /** Which embedder was configured via the dashboard (from .mega-compact.env). */
19
+ readonly configuredEmbedder: "trigram" | "http" | "minilm";
20
+ /** The URL configured in .mega-compact.env, if any. */
21
+ readonly configuredUrl: string | null;
22
+ /** True when the configured embedder differs from the active one (restart needed). */
23
+ readonly restartRequired: boolean;
18
24
  /** The embedding URL if httpEmbedder is active, otherwise null. */
19
25
  readonly embeddingUrl: string | null;
20
26
  /** The MEGACOMPACT_EMBED_CACHE value (number as string), or null. */
@@ -44,6 +44,7 @@ export function sendGatherDebug(res: ServerResponse, ctx: RouteContext): void {
44
44
  dashboardSnapshot: { error: "not fetched" },
45
45
  recentEvents: [],
46
46
  criticalEvents: [],
47
+ dataSourceAudit: {},
47
48
  };
48
49
 
49
50
  // 1. Config flags (no secrets — MEGACOMPACT_* only)
@@ -123,6 +124,68 @@ export function sendGatherDebug(res: ServerResponse, ctx: RouteContext): void {
123
124
  result.recentEvents = [{ error: e instanceof Error ? e.message : String(e) }];
124
125
  }
125
126
 
127
+ // 6. Data source audit — per-tab data population check
128
+ try {
129
+ const audit: Record<string, unknown> = {};
130
+ const db = openStoreDb(ctx);
131
+
132
+ // Main db tables
133
+ for (const table of ["context_chunks", "memories", "raw_transcript", "checkpoint_epochs", "dedup_mirror"]) {
134
+ try {
135
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get() as { c: number };
136
+ audit[`main.${table}`] = row.c;
137
+ } catch {
138
+ audit[`main.${table}`] = "table missing";
139
+ }
140
+ }
141
+
142
+ // context_health + cache_poison_events
143
+ for (const table of ["context_health", "cache_poison_events"]) {
144
+ try {
145
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get() as { c: number };
146
+ audit[`main.${table}`] = row.c;
147
+ } catch {
148
+ audit[`main.${table}`] = "table missing";
149
+ }
150
+ }
151
+
152
+ // turns.db tables
153
+ try {
154
+ const turnMod = require("../../src/store/turns/connection.js") as typeof import("../../src/store/turns/connection.js");
155
+ const tdb = turnMod.openTurnDb(ctx.stateDir);
156
+ for (const table of ["turns", "turn_recall", "session_conversations", "topics", "memory_topics"]) {
157
+ try {
158
+ const row = tdb.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get() as { c: number };
159
+ audit[`turns.${table}`] = row.c;
160
+ } catch {
161
+ audit[`turns.${table}`] = "table missing";
162
+ }
163
+ }
164
+ } catch (e) {
165
+ audit["turns.db"] = `error: ${e instanceof Error ? e.message : String(e)}`;
166
+ }
167
+
168
+ // .mega-compact.env
169
+ try {
170
+ const envPath = ctx.stateDir + "/.mega-compact.env";
171
+ audit["mega-compact.env"] = existsSync(envPath) ? "exists" : "not found";
172
+ } catch {
173
+ audit["mega-compact.env"] = "check failed";
174
+ }
175
+
176
+ // dashboard.json
177
+ try {
178
+ const metricsPath = defaultMetricsPath(ctx.stateDir);
179
+ audit["dashboard.json"] = existsSync(metricsPath) ? "exists" : "not found";
180
+ } catch {
181
+ audit["dashboard.json"] = "check failed";
182
+ }
183
+
184
+ (result as { dataSourceAudit: Record<string, unknown> }).dataSourceAudit = audit;
185
+ } catch (e) {
186
+ (result as { dataSourceAudit: unknown }).dataSourceAudit = { error: e instanceof Error ? e.message : String(e) };
187
+ }
188
+
126
189
  // Send (local helper to avoid importing sendJson from a non-exporting module)
127
190
  try {
128
191
  const body = JSON.stringify(result);
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { spawnSync } from "node:child_process"; // guardrails-allow PREVENT-PI-004: local subprocess detection only
13
13
  import { createRequire } from "node:module";
14
- import { writeFileSync, mkdirSync } from "node:fs";
14
+ import { writeFileSync, mkdirSync, readFileSync, existsSync } from "node:fs";
15
15
  import { join } from "node:path";
16
16
  import type { IncomingMessage, ServerResponse } from "node:http";
17
17
  import type { RouteContext } from "./routes-core.js";
@@ -37,10 +37,29 @@ function detectCurrentEmbedder(): SetupStatusResponse["currentEmbedder"] {
37
37
  return "trigram";
38
38
  }
39
39
 
40
+ /** Read the configured embedder from the .mega-compact.env file (written by
41
+ * the configure endpoint). This shows what the user SELECTED even before pi
42
+ * restarts and loads the env file into process.env. */
43
+ function detectConfiguredEmbedder(stateDir: string): { embedder: "trigram" | "http" | "minilm"; url: string | null } {
44
+ try {
45
+ const envPath = join(stateDir, ".mega-compact.env");
46
+ if (!existsSync(envPath)) return { embedder: "trigram", url: null };
47
+ const content = readFileSync(envPath, "utf-8");
48
+ const urlMatch = content.match(/^export\s+MEGACOMPACT_EMBEDDING_URL="([^"]+)"/m);
49
+ if (urlMatch) return { embedder: "http", url: urlMatch[1] };
50
+ const minilmMatch = content.match(/^export\s+MEGACOMPACT_MINILM="?(.+?)"?\s*$/m);
51
+ if (minilmMatch && ["1", "true", "yes"].includes(minilmMatch[1].trim().toLowerCase()))
52
+ return { embedder: "minilm", url: null };
53
+ return { embedder: "trigram", url: null };
54
+ } catch {
55
+ return { embedder: "trigram", url: null };
56
+ }
57
+ }
58
+
40
59
  export function handleSetupStatus(
41
60
  req: IncomingMessage,
42
61
  res: ServerResponse,
43
- _ctx: RouteContext,
62
+ ctx: RouteContext,
44
63
  ): boolean {
45
64
  if (req.url !== "/api/setup-status") return false;
46
65
  if (req.method !== "GET") {
@@ -49,9 +68,14 @@ export function handleSetupStatus(
49
68
  res.end(JSON.stringify({ error: "method_not_allowed" }));
50
69
  return true;
51
70
  }
71
+ const configured = detectConfiguredEmbedder(ctx.stateDir);
72
+ const active = detectCurrentEmbedder();
52
73
  const body: SetupStatusResponse = {
53
- currentEmbedder: detectCurrentEmbedder(),
54
- embeddingUrl: process.env["MEGACOMPACT_EMBEDDING_URL"] ?? null,
74
+ currentEmbedder: active,
75
+ configuredEmbedder: configured.embedder,
76
+ configuredUrl: configured.url,
77
+ restartRequired: active !== configured.embedder,
78
+ embeddingUrl: process.env["MEGACOMPACT_EMBEDDING_URL"] ?? configured.url,
55
79
  embedCache: process.env["MEGACOMPACT_EMBED_CACHE"] ?? null,
56
80
  minilm: ["1", "true", "yes"].includes(
57
81
  (process.env["MEGACOMPACT_MINILM"] ?? "").trim().toLowerCase(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
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",
@@ -206,7 +206,8 @@ export function buildTurnNodes(
206
206
  // ---------------------------------------------------------------------------
207
207
 
208
208
  export function buildTurnContentNodes(
209
- db: DatabaseSync,
209
+ turnDb: DatabaseSync,
210
+ mainDb: DatabaseSync,
210
211
  sessionId: string,
211
212
  nodes: MemoryGraphNode[],
212
213
  edges: MemoryGraphEdge[],
@@ -216,25 +217,58 @@ export function buildTurnContentNodes(
216
217
  // (identity_merge) merges them with the richest nodeType winning. Using the
217
218
  // global nodes array here would skip every node (Source A already added them).
218
219
  const seen = new Set<string>();
219
- const rows = sessionId
220
- ? (db
220
+
221
+ // turns live in turns.db, raw_transcript in the main db — can't JOIN across
222
+ // databases, so query separately and merge in JS.
223
+ const turnRows = sessionId
224
+ ? (turnDb
221
225
  .prepare(
222
- `SELECT t.turn_index, t.role, t.ended_at, r.content_bytes
223
- FROM turns t
224
- JOIN raw_transcript r ON r.session_id = t.session_id AND r.turn_index = t.turn_index
225
- WHERE t.session_id = ?
226
- ORDER BY t.turn_index ASC`,
226
+ `SELECT turn_index, role, ended_at
227
+ FROM turns
228
+ WHERE session_id = ?
229
+ ORDER BY turn_index ASC`,
227
230
  )
228
231
  .all(sessionId) as Array<Record<string, unknown>>)
229
- : (db
232
+ : (turnDb
230
233
  .prepare(
231
- `SELECT t.turn_index, t.role, t.ended_at, r.content_bytes
232
- FROM turns t
233
- JOIN raw_transcript r ON r.session_id = t.session_id AND r.turn_index = t.turn_index
234
- ORDER BY t.turn_index ASC`,
234
+ `SELECT turn_index, role, ended_at
235
+ FROM turns
236
+ ORDER BY turn_index ASC`,
235
237
  )
236
238
  .all() as Array<Record<string, unknown>>);
237
239
 
240
+ // Build a map of (session_id, turn_index) → content from raw_transcript.
241
+ const contentMap = new Map<string, string>();
242
+ try {
243
+ const transcriptRows = sessionId
244
+ ? (mainDb
245
+ .prepare(
246
+ `SELECT turn_index, content_bytes FROM raw_transcript WHERE session_id = ?`,
247
+ )
248
+ .all(sessionId) as Array<Record<string, unknown>>)
249
+ : (mainDb
250
+ .prepare(
251
+ `SELECT turn_index, content_bytes FROM raw_transcript`,
252
+ )
253
+ .all() as Array<Record<string, unknown>>);
254
+ for (const r of transcriptRows) {
255
+ contentMap.set(String(r.turn_index), String(r.content_bytes ?? ""));
256
+ }
257
+ } catch {
258
+ // raw_transcript may not exist in the main db (dbMirror off) — return empty.
259
+ return;
260
+ }
261
+
262
+ // Merge: only produce nodes where both a turn row AND transcript content exist.
263
+ const rows = turnRows
264
+ .filter((t) => contentMap.has(String(t.turn_index)))
265
+ .map((t) => ({
266
+ turn_index: t.turn_index,
267
+ role: t.role,
268
+ ended_at: t.ended_at,
269
+ content_bytes: contentMap.get(String(t.turn_index)) ?? "",
270
+ }));
271
+
238
272
  if (rows.length === 0) return;
239
273
 
240
274
  const turnContentNodes: MemoryGraphNode[] = [];
@@ -269,7 +303,7 @@ export function buildTurnContentNodes(
269
303
  if (turnContentNodes.length > 1) {
270
304
  const embeddings = turnContentNodes.map((n) => {
271
305
  try {
272
- return { id: n.id, vec: getOrComputeEmbedding(db, n.textSnippet) };
306
+ return { id: n.id, vec: getOrComputeEmbedding(mainDb, n.textSnippet) };
273
307
  } catch {
274
308
  return { id: n.id, vec: null as number[] | null };
275
309
  }
@@ -291,7 +325,7 @@ export function buildTurnContentNodes(
291
325
  }
292
326
  }
293
327
 
294
- linkTurnToCheckpointEdges(db, sessionId, turnContentNodes, edges);
328
+ linkTurnToCheckpointEdges(mainDb, sessionId, turnContentNodes, edges);
295
329
  }
296
330
 
297
331
  nodes.push(...turnContentNodes);
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { getStateDir } from "./store.js";
16
16
  import { openStore } from "./store/sqlite/utils.js";
17
+ import { openTurnStore } from "./store/turns/connection.js";
17
18
  import type { DatabaseSync } from "node:sqlite";
18
19
  import { Logger } from "./log.js";
19
20
  import { runValidationPipeline } from "./memoryGraph/gates.js";
@@ -182,14 +183,23 @@ function buildUnvalidatedGraph(
182
183
  return { nodes: [], edges: [] };
183
184
  }
184
185
 
186
+ // Turns live in the isolated turns.db, not the main sqlite.db. Open it
187
+ // separately so buildTurnNodes/buildTurnContentNodes can query it.
188
+ let turnDb: DatabaseSync | null = null;
189
+ try {
190
+ turnDb = openTurnStore(stateDir);
191
+ } catch {
192
+ log.warn("graph_source_unavailable", { detail: "Cannot open turns.db" });
193
+ }
194
+
185
195
  try {
186
196
  // Source: checkpoints (always on)
187
197
  buildCheckpointNodes(db, sessionId, nodes, edges);
188
198
 
189
- // Source A: turns (structural, metadata-only)
190
- if (areTurnsEnabled()) {
199
+ // Source A: turns (structural, metadata-only) — from turns.db, not main db
200
+ if (areTurnsEnabled() && turnDb) {
191
201
  try {
192
- buildTurnNodes(db, sessionId, nodes, edges);
202
+ buildTurnNodes(turnDb, sessionId, nodes, edges);
193
203
  } catch (err) {
194
204
  log.warn("graph_source_unavailable", {
195
205
  source: "turns",
@@ -199,9 +209,11 @@ function buildUnvalidatedGraph(
199
209
  }
200
210
 
201
211
  // Source B: turn content (raw_transcript join, embedding-based)
202
- if (isTurnContentEnabled()) {
212
+ // raw_transcript is in the main db, but turns are in turns.db — the JOIN
213
+ // can't span databases, so we pass both and merge in JS.
214
+ if (isTurnContentEnabled() && turnDb) {
203
215
  try {
204
- buildTurnContentNodes(db, sessionId, nodes, edges);
216
+ buildTurnContentNodes(turnDb, db, sessionId, nodes, edges);
205
217
  } catch (err) {
206
218
  log.warn("graph_source_unavailable", {
207
219
  source: "turn-content",
@@ -16,10 +16,26 @@
16
16
  import type { DatabaseSync } from "node:sqlite";
17
17
 
18
18
  /** Schema version stamp (written once into turns_meta). */
19
- const SCHEMA_VERSION = 1;
19
+ const SCHEMA_VERSION = 2;
20
20
 
21
21
  /** Create the unified turn-store schema if absent. Idempotent. */
22
22
  export function initTurnSchema(db: DatabaseSync): void {
23
+ // Migration: v1 had a pressure_band CHECK constraint that only allowed
24
+ // 'green','yellow','red' — but the runtime sends 'low','medium','high',
25
+ // 'ultra','mega'. Every INSERT failed silently. Since the constraint
26
+ // blocked ALL writes, the turns table is guaranteed empty — safe to
27
+ // drop and recreate with the widened constraint.
28
+ try {
29
+ const tableSql = db
30
+ .prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='turns'")
31
+ .get() as { sql?: string } | undefined;
32
+ if (tableSql?.sql && tableSql.sql.includes("'green'") && !tableSql.sql.includes("'low'")) {
33
+ db.exec("DROP TABLE IF EXISTS turns");
34
+ }
35
+ } catch {
36
+ /* non-fatal: table may not exist yet */
37
+ }
38
+
23
39
  // ── Contract tables (master's SqliteTurnStore shape) ──────────────────
24
40
  db.exec(`
25
41
  CREATE TABLE IF NOT EXISTS turns_meta (
@@ -41,7 +57,7 @@ export function initTurnSchema(db: DatabaseSync): void {
41
57
  ended_at INTEGER NOT NULL,
42
58
  ctx_tokens INTEGER,
43
59
  ctx_percent REAL,
44
- pressure_band TEXT CHECK(pressure_band IS NULL OR pressure_band IN ('green','yellow','red')),
60
+ pressure_band TEXT CHECK(pressure_band IS NULL OR pressure_band IN ('low','medium','high','ultra','mega','green','yellow','red')),
45
61
  model TEXT,
46
62
  epoch_id TEXT,
47
63
  UNIQUE(conversation_id, turn_index)
@@ -1,2 +0,0 @@
1
- import{r as l,e as k,g as R,h as D,j as e}from"./index-C9dTXvDQ.js";const t={container:{padding:"1rem",maxWidth:"720px",margin:"0 auto",fontFamily:"system-ui, sans-serif",color:"#e0e0e0"},section:{background:"#1a1a2e",borderRadius:"8px",padding:"1rem",marginBottom:"1rem",border:"1px solid #2a2a4e"},sectionTitle:{fontSize:"1.1rem",fontWeight:700,marginTop:0,marginBottom:"0.75rem",color:"#e0e0e0",borderBottom:"1px solid #333",paddingBottom:"0.5rem"},label:{fontWeight:600,color:"#a0a0c0",marginRight:"0.5rem"},value:{color:"#e0e0e0"},row:{marginBottom:"0.4rem",fontSize:"0.9rem"},warning:{background:"#3a2a00",border:"1px solid #665500",borderRadius:"8px",padding:"0.75rem",marginTop:"0.5rem",color:"#ffcc00",fontSize:"0.85rem"},badge:{display:"inline-block",padding:"0.15rem 0.5rem",borderRadius:"4px",fontSize:"0.8rem",fontWeight:600,marginLeft:"0.5rem"},detectRow:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"0.5rem 0",borderBottom:"1px solid #2a2a4e"},button:{background:"#3a5a9f",color:"#fff",border:"none",borderRadius:"6px",padding:"0.5rem 1rem",cursor:"pointer",fontSize:"0.9rem",fontWeight:600}};function B(){const[s,S]=l.useState(null),[n,v]=l.useState(null),[c,E]=l.useState(null),[g,x]=l.useState(null),[f,m]=l.useState(!1),[i,h]=l.useState(null),[d,j]=l.useState(null),[b,y]=l.useState(null),o=l.useCallback(()=>{k().then(S).catch(r=>E(r instanceof Error?r.message:String(r)))},[]),u=l.useCallback(r=>{h(r),y(null),j(null),R({embedder:r}).then(a=>{j(a),h(null),o()}).catch(a=>{y(a instanceof Error?a.message:String(a)),h(null)})},[o]),C=l.useCallback(()=>{m(!0),x(null),D().then(r=>{v(r),m(!1)}).catch(r=>{x(r instanceof Error?r.message:String(r)),m(!1)})},[]);l.useEffect(()=>{o()},[o]),l.useEffect(()=>{const r=setInterval(o,3e4);return()=>clearInterval(r)},[o]);const w=r=>{switch(r){case"trigram":return"TrigramEmbedder (heuristic, default)";case"http":return"HTTP Embedder (BYO localhost)";case"minilm":return"MiniLM (experimental)";default:return"Unknown"}},z=s?.currentEmbedder==="trigram"&&e.jsxs("div",{style:t.warning,children:[e.jsx("strong",{children:"Note:"})," TrigramEmbedder is a heuristic-strength default embedder. It works without any setup but recall quality may be lower than a dedicated embedding backend. If you notice poor recall results, consider installing Ollama and running the"," ",e.jsx("code",{children:"/megasetup"})," command, or set"," ",e.jsx("code",{children:"MEGACOMPACT_EMBEDDING_URL"})," to point at your own localhost embedding server."]}),p=r=>{const a=r?"#1a5a1a":"#3a1a1a",T=r?"#4caf50":"#f44336";return e.jsx("span",{style:{...t.badge,background:a,color:T},children:r?"Detected":"Not found"})};return e.jsxs("div",{style:t.container,children:[e.jsx("h2",{style:{fontSize:"1.3rem",marginTop:0,marginBottom:"1rem"},children:"Embedder Setup"}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"Current Embedder Configuration"}),c&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.85rem"},children:["Error: ",c]}),s&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Active Embedder:"}),e.jsx("span",{style:t.value,children:w(s.currentEmbedder)})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Embedding URL:"}),e.jsx("span",{style:t.value,children:s.embeddingUrl??e.jsx("span",{style:{color:"#888"},children:"not set"})})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Embed Cache:"}),e.jsx("span",{style:t.value,children:s.embedCache??e.jsx("span",{style:{color:"#888"},children:"not set"})})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"MiniLM:"}),e.jsx("span",{style:t.value,children:s.minilm?"enabled":"disabled"})]}),z]}),!s&&!c&&e.jsx("p",{style:{color:"#888",fontSize:"0.85rem"},children:"Loading configuration..."})]}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"Local Embedder Detection"}),e.jsx("p",{style:{fontSize:"0.85rem",color:"#a0a0c0",marginTop:0},children:"Detect which local embedding backends are available on this machine. No software is installed — detection only."}),n&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"Ollama"})}),n.ollama!==null?e.jsxs("span",{children:[p(n.ollama.installed),n.ollama.installed&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.ollama.running?`server running, ${n.ollama.models.length} model(s)`:"server not running"})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"llama.cpp"})}),n.llamaCpp!==null?e.jsxs("span",{children:[p(n.llamaCpp.installed),n.llamaCpp.installed&&n.llamaCpp.detail&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.llamaCpp.detail})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"ONNX Runtime"})}),n.onnx!==null?e.jsxs("span",{children:[p(n.onnx.installed),n.onnx.installed&&n.onnx.detail&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.onnx.detail})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),n.error&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.8rem",marginTop:"0.5rem"},children:["Detection error: ",n.error]})]}),g&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.85rem"},children:["Error: ",g]}),e.jsxs("div",{style:{marginTop:"1rem"},children:[e.jsx("button",{style:t.button,onClick:C,disabled:f,children:f?"Detecting...":"Run Detection"}),e.jsxs("span",{style:{fontSize:"0.8rem",color:"#888",marginLeft:"0.75rem"},children:["Or run ",e.jsx("code",{children:"/megasetup"})," in the chat for an interactive wizard."]})]}),n&&e.jsxs("div",{style:{marginTop:"1rem",paddingTop:"0.75rem",borderTop:"1px solid #2a2a4e"},children:[e.jsx("div",{style:{fontSize:"0.9rem",fontWeight:600,marginBottom:"0.5rem"},children:"Configure"}),e.jsxs("div",{style:{display:"flex",gap:"0.5rem",flexWrap:"wrap"},children:[e.jsx("button",{style:{...t.button,opacity:n.ollama?.installed?1:.4},onClick:()=>u("ollama"),disabled:!n.ollama?.installed||i!==null,children:i==="ollama"?"Writing...":"Use Ollama"}),e.jsx("button",{style:{...t.button,opacity:n.llamaCpp?.installed?1:.4},onClick:()=>u("llama"),disabled:!n.llamaCpp?.installed||i!==null,children:i==="llama"?"Writing...":"Use llama.cpp"}),e.jsx("button",{style:{...t.button,background:"#444"},onClick:()=>u("trigram"),disabled:i!==null,children:i==="trigram"?"Writing...":"Use Trigram (default)"})]}),d&&e.jsx("div",{style:t.warning,children:d.alreadyActive?"Already active — no restart needed.":`Configured ${d.embedder}. Restart pi to activate (config written to ${d.envPath}).`}),b&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.85rem",marginTop:"0.5rem"},children:["Configure error: ",b]})]})]}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"How to Upgrade"}),e.jsxs("ul",{style:{fontSize:"0.85rem",lineHeight:1.6,paddingLeft:"1.25rem"},children:[e.jsxs("li",{children:[e.jsx("strong",{children:"Ollama:"})," Install from"," ",e.jsx("a",{href:"https://ollama.com",style:{color:"#6a9fff"},target:"_blank",rel:"noopener noreferrer",children:"ollama.com"}),", then ",e.jsx("code",{children:"ollama pull nomic-embed-text"})," and restart pi. Set ",e.jsx("code",{children:"MEGACOMPACT_EMBEDDING_URL=http://localhost:11434/api/embeddings"}),"."]}),e.jsxs("li",{children:[e.jsx("strong",{children:"llama.cpp:"})," Build or install llama.cpp, run"," ",e.jsx("code",{children:"llama-server"})," with an embedding model, and set the URL accordingly."]}),e.jsxs("li",{children:[e.jsx("strong",{children:"ONNX Runtime:"})," Install"," ",e.jsx("code",{children:"onnxruntime-node"})," in the project root. Not yet integrated — see docs for manual setup."]})]})]})]})}export{B as default};
2
- //# sourceMappingURL=SetupTab-rKGgWpI0.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SetupTab-rKGgWpI0.js","sources":["../../src/tabs/SetupTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/SetupTab.tsx — Setup wizard tab (P0b).\n *\n * Shows current embedder configuration, warns about trigram recall quality,\n * and mirrors the /megasetup command with detection results.\n *\n * PREVENT-PI-004: all data comes from localhost dashboard API endpoints —\n * no external network calls.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { SetupStatusResponse, SetupDetectResponse, SetupConfigureResponse } from \"@contracts\";\nimport { fetchSetupStatus, fetchSetupDetect, configureEmbedder } from \"../api/client\";\n\n// ---------------------------------------------------------------------------\n// Style constants\n// ---------------------------------------------------------------------------\n\nconst styles: Record<string, React.CSSProperties> = {\n\tcontainer: {\n\t\tpadding: \"1rem\",\n\t\tmaxWidth: \"720px\",\n\t\tmargin: \"0 auto\",\n\t\tfontFamily: \"system-ui, sans-serif\",\n\t\tcolor: \"#e0e0e0\",\n\t},\n\tsection: {\n\t\tbackground: \"#1a1a2e\",\n\t\tborderRadius: \"8px\",\n\t\tpadding: \"1rem\",\n\t\tmarginBottom: \"1rem\",\n\t\tborder: \"1px solid #2a2a4e\",\n\t},\n\tsectionTitle: {\n\t\tfontSize: \"1.1rem\",\n\t\tfontWeight: 700,\n\t\tmarginTop: 0,\n\t\tmarginBottom: \"0.75rem\",\n\t\tcolor: \"#e0e0e0\",\n\t\tborderBottom: \"1px solid #333\",\n\t\tpaddingBottom: \"0.5rem\",\n\t},\n\tlabel: { fontWeight: 600, color: \"#a0a0c0\", marginRight: \"0.5rem\" },\n\tvalue: { color: \"#e0e0e0\" },\n\trow: { marginBottom: \"0.4rem\", fontSize: \"0.9rem\" },\n\twarning: {\n\t\tbackground: \"#3a2a00\",\n\t\tborder: \"1px solid #665500\",\n\t\tborderRadius: \"8px\",\n\t\tpadding: \"0.75rem\",\n\t\tmarginTop: \"0.5rem\",\n\t\tcolor: \"#ffcc00\",\n\t\tfontSize: \"0.85rem\",\n\t},\n\tbadge: {\n\t\tdisplay: \"inline-block\",\n\t\tpadding: \"0.15rem 0.5rem\",\n\t\tborderRadius: \"4px\",\n\t\tfontSize: \"0.8rem\",\n\t\tfontWeight: 600,\n\t\tmarginLeft: \"0.5rem\",\n\t},\n\tdetectRow: {\n\t\tdisplay: \"flex\",\n\t\tjustifyContent: \"space-between\",\n\t\talignItems: \"center\",\n\t\tpadding: \"0.5rem 0\",\n\t\tborderBottom: \"1px solid #2a2a4e\",\n\t},\n\tbutton: {\n\t\tbackground: \"#3a5a9f\",\n\t\tcolor: \"#fff\",\n\t\tborder: \"none\",\n\t\tborderRadius: \"6px\",\n\t\tpadding: \"0.5rem 1rem\",\n\t\tcursor: \"pointer\",\n\t\tfontSize: \"0.9rem\",\n\t\tfontWeight: 600,\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// SetupTab component\n// ---------------------------------------------------------------------------\n\nexport default function SetupTab(): React.ReactElement {\n\tconst [status, setStatus] = useState<SetupStatusResponse | null>(null);\n\tconst [detect, setDetect] = useState<SetupDetectResponse | null>(null);\n\tconst [statusError, setStatusError] = useState<string | null>(null);\n\tconst [detectError, setDetectError] = useState<string | null>(null);\n\tconst [runningDetect, setRunningDetect] = useState(false);\n\tconst [configuring, setConfiguring] = useState<string | null>(null);\n\tconst [configResult, setConfigResult] = useState<SetupConfigureResponse | null>(null);\n\tconst [configError, setConfigError] = useState<string | null>(null);\n\n\tconst loadStatus = useCallback(() => {\n\t\tfetchSetupStatus()\n\t\t\t.then(setStatus)\n\t\t\t.catch((e: unknown) =>\n\t\t\t\tsetStatusError(e instanceof Error ? e.message : String(e)),\n\t\t\t);\n\t}, []);\n\n\tconst applyEmbedder = useCallback((embedder: \"ollama\" | \"llama\" | \"trigram\") => {\n\t\tsetConfiguring(embedder);\n\t\tsetConfigError(null);\n\t\tsetConfigResult(null);\n\t\tconfigureEmbedder({ embedder })\n\t\t\t.then((r) => {\n\t\t\t\tsetConfigResult(r);\n\t\t\t\tsetConfiguring(null);\n\t\t\t\tloadStatus();\n\t\t\t})\n\t\t\t.catch((e: unknown) => {\n\t\t\t\tsetConfigError(e instanceof Error ? e.message : String(e));\n\t\t\t\tsetConfiguring(null);\n\t\t\t});\n\t}, [loadStatus]);\n\n\tconst runDetect = useCallback(() => {\n\t\tsetRunningDetect(true);\n\t\tsetDetectError(null);\n\t\tfetchSetupDetect()\n\t\t\t.then((d) => {\n\t\t\t\tsetDetect(d);\n\t\t\t\tsetRunningDetect(false);\n\t\t\t})\n\t\t\t.catch((e: unknown) => {\n\t\t\t\tsetDetectError(e instanceof Error ? e.message : String(e));\n\t\t\t\tsetRunningDetect(false);\n\t\t\t});\n\t}, []);\n\n\tuseEffect(() => {\n\t\tloadStatus();\n\t}, [loadStatus]);\n\n\t// Poll status every 30s\n\tuseEffect(() => {\n\t\tconst id = setInterval(loadStatus, 30000);\n\t\treturn () => clearInterval(id);\n\t}, [loadStatus]);\n\n\t// -----------------------------------------------------------------------\n\t// Render helpers\n\t// -----------------------------------------------------------------------\n\n\tconst embedderLabel = (e: string): string => {\n\t\tswitch (e) {\n\t\t\tcase \"trigram\":\n\t\t\t\treturn \"TrigramEmbedder (heuristic, default)\";\n\t\t\tcase \"http\":\n\t\t\t\treturn \"HTTP Embedder (BYO localhost)\";\n\t\t\tcase \"minilm\":\n\t\t\t\treturn \"MiniLM (experimental)\";\n\t\t\tdefault:\n\t\t\t\treturn \"Unknown\";\n\t\t}\n\t};\n\n\tconst trigramWarning = status?.currentEmbedder === \"trigram\" && (\n\t\t<div style={styles.warning}>\n\t\t\t<strong>Note:</strong> TrigramEmbedder is a heuristic-strength default\n\t\t\tembedder. It works without any setup but recall quality may be lower\n\t\t\tthan a dedicated embedding backend. If you notice poor recall results,\n\t\t\tconsider installing Ollama and running the{\" \"}\n\t\t\t<code>/megasetup</code> command, or set{\" \"}\n\t\t\t<code>MEGACOMPACT_EMBEDDING_URL</code> to point at your own localhost\n\t\t\tembedding server.\n\t\t</div>\n\t);\n\n\tconst detectBadge = (installed: boolean): React.ReactElement => {\n\t\tconst bg = installed ? \"#1a5a1a\" : \"#3a1a1a\";\n\t\tconst color = installed ? \"#4caf50\" : \"#f44336\";\n\t\treturn (\n\t\t\t<span style={{ ...styles.badge, background: bg, color }}>\n\t\t\t\t{installed ? \"Detected\" : \"Not found\"}\n\t\t\t</span>\n\t\t);\n\t};\n\n\t// -----------------------------------------------------------------------\n\t// Main render\n\t// -----------------------------------------------------------------------\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h2 style={{ fontSize: \"1.3rem\", marginTop: 0, marginBottom: \"1rem\" }}>\n\t\t\t\tEmbedder Setup\n\t\t\t</h2>\n\n\t\t\t{/* Current Config Section */}\n\t\t\t<div style={styles.section}>\n\t\t\t\t<h3 style={styles.sectionTitle}>Current Embedder Configuration</h3>\n\t\t\t\t{statusError && (\n\t\t\t\t\t<p style={{ color: \"#f44336\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\tError: {statusError}\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t\t{status && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<div style={styles.row}>\n\t\t\t\t\t\t\t<span style={styles.label}>Active Embedder:</span>\n\t\t\t\t\t\t\t<span style={styles.value}>\n\t\t\t\t\t\t\t\t{embedderLabel(status.currentEmbedder)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={styles.row}>\n\t\t\t\t\t\t\t<span style={styles.label}>Embedding URL:</span>\n\t\t\t\t\t\t\t<span style={styles.value}>\n\t\t\t\t\t\t\t\t{status.embeddingUrl ?? (\n\t\t\t\t\t\t\t\t\t<span style={{ color: \"#888\" }}>not set</span>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={styles.row}>\n\t\t\t\t\t\t\t<span style={styles.label}>Embed Cache:</span>\n\t\t\t\t\t\t\t<span style={styles.value}>\n\t\t\t\t\t\t\t\t{status.embedCache ?? (\n\t\t\t\t\t\t\t\t\t<span style={{ color: \"#888\" }}>not set</span>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={styles.row}>\n\t\t\t\t\t\t\t<span style={styles.label}>MiniLM:</span>\n\t\t\t\t\t\t\t<span style={styles.value}>{status.minilm ? \"enabled\" : \"disabled\"}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{trigramWarning}\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t\t{!status && !statusError && (\n\t\t\t\t\t<p style={{ color: \"#888\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\tLoading configuration...\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Detection Section */}\n\t\t\t<div style={styles.section}>\n\t\t\t\t<h3 style={styles.sectionTitle}>Local Embedder Detection</h3>\n\t\t\t\t<p style={{ fontSize: \"0.85rem\", color: \"#a0a0c0\", marginTop: 0 }}>\n\t\t\t\t\tDetect which local embedding backends are available on this machine.\n\t\t\t\t\tNo software is installed — detection only.\n\t\t\t\t</p>\n\n\t\t\t\t{detect && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t{/* Ollama */}\n\t\t\t\t\t\t<div style={styles.detectRow}>\n\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t<strong>Ollama</strong>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{detect.ollama !== null ? (\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t{detectBadge(detect.ollama.installed)}\n\t\t\t\t\t\t\t\t\t{detect.ollama.installed && (\n\t\t\t\t\t\t\t\t\t\t<span style={{ fontSize: \"0.8rem\", color: \"#a0a0c0\", marginLeft: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\t\t\t\t{detect.ollama.running\n\t\t\t\t\t\t\t\t\t\t\t\t? `server running, ${detect.ollama.models.length} model(s)`\n\t\t\t\t\t\t\t\t\t\t\t\t: \"server not running\"}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span style={{ color: \"#888\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\t\t\t\tnot checked\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* llama.cpp */}\n\t\t\t\t\t\t<div style={styles.detectRow}>\n\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t<strong>llama.cpp</strong>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{detect.llamaCpp !== null ? (\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t{detectBadge(detect.llamaCpp.installed)}\n\t\t\t\t\t\t\t\t\t{detect.llamaCpp.installed && detect.llamaCpp.detail && (\n\t\t\t\t\t\t\t\t\t\t<span style={{ fontSize: \"0.8rem\", color: \"#a0a0c0\", marginLeft: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\t\t\t\t{detect.llamaCpp.detail}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span style={{ color: \"#888\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\t\t\t\tnot checked\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* ONNX */}\n\t\t\t\t\t\t<div style={styles.detectRow}>\n\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t<strong>ONNX Runtime</strong>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{detect.onnx !== null ? (\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t{detectBadge(detect.onnx.installed)}\n\t\t\t\t\t\t\t\t\t{detect.onnx.installed && detect.onnx.detail && (\n\t\t\t\t\t\t\t\t\t\t<span style={{ fontSize: \"0.8rem\", color: \"#a0a0c0\", marginLeft: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\t\t\t\t{detect.onnx.detail}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span style={{ color: \"#888\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\t\t\t\tnot checked\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{detect.error && (\n\t\t\t\t\t\t\t<p style={{ color: \"#f44336\", fontSize: \"0.8rem\", marginTop: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\tDetection error: {detect.error}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</>\n\t\t\t\t)}\n\n\t\t\t\t{detectError && (\n\t\t\t\t\t<p style={{ color: \"#f44336\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\tError: {detectError}\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\n\t\t\t\t<div style={{ marginTop: \"1rem\" }}>\n\t\t\t\t\t<button\n\t\t\t\t\t\tstyle={styles.button}\n\t\t\t\t\t\tonClick={runDetect}\n\t\t\t\t\t\tdisabled={runningDetect}\n\t\t\t\t\t>\n\t\t\t\t\t\t{runningDetect ? \"Detecting...\" : \"Run Detection\"}\n\t\t\t\t\t</button>\n\t\t\t\t\t<span style={{ fontSize: \"0.8rem\", color: \"#888\", marginLeft: \"0.75rem\" }}>\n\t\t\t\t\t\tOr run <code>/megasetup</code> in the chat for an interactive wizard.\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Configure buttons — appear after detection runs */}\n\t\t\t\t{detect && (\n\t\t\t\t\t<div style={{ marginTop: \"1rem\", paddingTop: \"0.75rem\", borderTop: \"1px solid #2a2a4e\" }}>\n\t\t\t\t\t\t<div style={{ fontSize: \"0.9rem\", fontWeight: 600, marginBottom: \"0.5rem\" }}>\n\t\t\t\t\t\t\tConfigure\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={{ display: \"flex\", gap: \"0.5rem\", flexWrap: \"wrap\" }}>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tstyle={{ ...styles.button, opacity: detect.ollama?.installed ? 1 : 0.4 }}\n\t\t\t\t\t\t\t\tonClick={() => applyEmbedder(\"ollama\")}\n\t\t\t\t\t\t\t\tdisabled={!detect.ollama?.installed || configuring !== null}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{configuring === \"ollama\" ? \"Writing...\" : \"Use Ollama\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tstyle={{ ...styles.button, opacity: detect.llamaCpp?.installed ? 1 : 0.4 }}\n\t\t\t\t\t\t\t\tonClick={() => applyEmbedder(\"llama\")}\n\t\t\t\t\t\t\t\tdisabled={!detect.llamaCpp?.installed || configuring !== null}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{configuring === \"llama\" ? \"Writing...\" : \"Use llama.cpp\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tstyle={{ ...styles.button, background: \"#444\" }}\n\t\t\t\t\t\t\t\tonClick={() => applyEmbedder(\"trigram\")}\n\t\t\t\t\t\t\t\tdisabled={configuring !== null}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{configuring === \"trigram\" ? \"Writing...\" : \"Use Trigram (default)\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{configResult && (\n\t\t\t\t\t\t\t<div style={styles.warning}>\n\t\t\t\t\t\t\t\t{configResult.alreadyActive\n\t\t\t\t\t\t\t\t\t? \"Already active — no restart needed.\"\n\t\t\t\t\t\t\t\t\t: `Configured ${configResult.embedder}. Restart pi to activate (config written to ${configResult.envPath}).`}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{configError && (\n\t\t\t\t\t\t\t<p style={{ color: \"#f44336\", fontSize: \"0.85rem\", marginTop: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\tConfigure error: {configError}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Help Section */}\n\t\t\t<div style={styles.section}>\n\t\t\t\t<h3 style={styles.sectionTitle}>How to Upgrade</h3>\n\t\t\t\t<ul style={{ fontSize: \"0.85rem\", lineHeight: 1.6, paddingLeft: \"1.25rem\" }}>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Ollama:</strong> Install from{\" \"}\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref=\"https://ollama.com\"\n\t\t\t\t\t\t\tstyle={{ color: \"#6a9fff\" }}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tollama.com\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t, then <code>ollama pull nomic-embed-text</code> and restart pi.\n\t\t\t\t\t\tSet <code>MEGACOMPACT_EMBEDDING_URL=http://localhost:11434/api/embeddings</code>.\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>llama.cpp:</strong> Build or install llama.cpp, run{\" \"}\n\t\t\t\t\t\t<code>llama-server</code> with an embedding model, and set\n\t\t\t\t\t\tthe URL accordingly.\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>ONNX Runtime:</strong> Install{\" \"}\n\t\t\t\t\t\t<code>onnxruntime-node</code> in the project root. Not yet\n\t\t\t\t\t\tintegrated — see docs for manual setup.\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["styles","SetupTab","status","setStatus","useState","detect","setDetect","statusError","setStatusError","detectError","setDetectError","runningDetect","setRunningDetect","configuring","setConfiguring","configResult","setConfigResult","configError","setConfigError","loadStatus","useCallback","fetchSetupStatus","e","applyEmbedder","embedder","configureEmbedder","r","runDetect","fetchSetupDetect","d","useEffect","id","embedderLabel","trigramWarning","jsx","detectBadge","installed","bg","color","jsxs","Fragment"],"mappings":"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"}