pi-mega-compact 0.11.7 → 0.11.9

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 (93) hide show
  1. package/dist/extensions/dashboard-server/gather-debug-keys.js +18 -0
  2. package/dist/extensions/dashboard-server/gather-debug.js +129 -0
  3. package/dist/extensions/dashboard-server/routes-maintenance.js +6 -0
  4. package/dist/extensions/dashboard-server/routes-memory-map.js +8 -15
  5. package/dist/extensions/mega-commands.js +65 -5
  6. package/dist/extensions/mega-config.js +7 -0
  7. package/dist/extensions/mega-events/agent-handlers.js +74 -5
  8. package/dist/extensions/mega-events/context-handler.js +132 -3
  9. package/dist/extensions/mega-runtime/snapshot.js +1 -1
  10. package/dist/extensions/mega-trim.js +10 -0
  11. package/dist/src/config/turns.js +2 -1
  12. package/dist/src/memoryGraph/embedding.js +50 -0
  13. package/dist/src/memoryGraph/gates.js +379 -0
  14. package/dist/src/memoryGraph/sources.js +390 -0
  15. package/dist/src/memoryGraph.js +133 -259
  16. package/dist/src/store/sqlite/embedding-cache.js +71 -0
  17. package/dist/src/store/sqlite.js +1 -0
  18. package/dist/src/topics/cluster.js +10 -2
  19. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js → AchievementTiles-DFitrb4Y.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js.map → AchievementTiles-DFitrb4Y.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js → AchievementsTab-PJrmq8pa.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js.map → AchievementsTab-PJrmq8pa.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js +2 -0
  24. package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js.map +1 -0
  25. package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js → ConfigTab-Dx-RpO8C.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js.map → ConfigTab-Dx-RpO8C.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js → EventsTab-C63BEWEs.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js.map → EventsTab-C63BEWEs.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js → GameTab-DHpJE6km.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js.map → GameTab-DHpJE6km.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js +2 -0
  32. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js.map +1 -0
  33. package/extensions/dashboard-client/dist/assets/MemoryMapTab-Cg1fgEx_.js +2 -0
  34. package/extensions/dashboard-client/dist/assets/MemoryMapTab-Cg1fgEx_.js.map +1 -0
  35. package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js → MetricsTab-DCahxNOQ.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js.map → MetricsTab-DCahxNOQ.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js → OverviewTab-RYvO1SZL.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js.map → OverviewTab-RYvO1SZL.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js → ReposTab-BDGr_UXb.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js.map → ReposTab-BDGr_UXb.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js → SessionsTab-ByrTnHaB.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js.map → SessionsTab-ByrTnHaB.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js → SetupTab-CELafKis.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js.map → SetupTab-CELafKis.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js → TimeSavedCard-BLWtmfr_.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js.map → TimeSavedCard-BLWtmfr_.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/TopicsTab-8Fsh_Igx.js +2 -0
  48. package/extensions/dashboard-client/dist/assets/TopicsTab-8Fsh_Igx.js.map +1 -0
  49. package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js → TurnsTab-D7mgXDe2.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js.map → TurnsTab-D7mgXDe2.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js → index-h3jgEs6G.js} +10 -10
  52. package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js.map → index-h3jgEs6G.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js → useSSE-C5lmipBF.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-C5lmipBF.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/index.html +1 -1
  56. package/extensions/dashboard-client/src/api/client.ts +5 -0
  57. package/extensions/dashboard-client/src/memory-map-layout.ts +161 -0
  58. package/extensions/dashboard-client/src/memory-map-shapes.tsx +126 -0
  59. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +29 -2
  60. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +101 -0
  61. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +201 -165
  62. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +21 -0
  63. package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +1 -1
  64. package/extensions/dashboard-server/api-contracts/index.ts +3 -1
  65. package/extensions/dashboard-server/api-contracts/maintenance.ts +13 -0
  66. package/extensions/dashboard-server/api-contracts/memory-map.ts +14 -6
  67. package/extensions/dashboard-server/gather-debug-keys.ts +18 -0
  68. package/extensions/dashboard-server/gather-debug.ts +136 -0
  69. package/extensions/dashboard-server/routes-maintenance.ts +7 -0
  70. package/extensions/dashboard-server/routes-memory-map.ts +21 -29
  71. package/extensions/mega-commands.ts +63 -6
  72. package/extensions/mega-config.ts +22 -0
  73. package/extensions/mega-events/agent-handlers.ts +88 -5
  74. package/extensions/mega-events/context-handler.ts +152 -3
  75. package/extensions/mega-runtime/snapshot.ts +1 -1
  76. package/extensions/mega-trim.ts +15 -0
  77. package/package.json +1 -1
  78. package/src/config/turns.ts +5 -2
  79. package/src/memoryGraph/embedding.ts +63 -0
  80. package/src/memoryGraph/gates.ts +451 -0
  81. package/src/memoryGraph/sources.ts +484 -0
  82. package/src/memoryGraph.ts +188 -350
  83. package/src/store/sqlite/embedding-cache.ts +81 -0
  84. package/src/store/sqlite.ts +1 -0
  85. package/src/topics/cluster.ts +11 -1
  86. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js +0 -2
  87. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js.map +0 -1
  88. package/extensions/dashboard-client/dist/assets/MaintenanceTab-P4SUsvMf.js +0 -2
  89. package/extensions/dashboard-client/dist/assets/MaintenanceTab-P4SUsvMf.js.map +0 -1
  90. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +0 -2
  91. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +0 -1
  92. package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js +0 -2
  93. package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{r as e}from"./index-DrktNOoT.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-Bf5LOLSi.js.map
1
+ import{r as e}from"./index-h3jgEs6G.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-C5lmipBF.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSSE-Bf5LOLSi.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-C5lmipBF.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-DrktNOoT.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-h3jgEs6G.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/index-CUNn22iV.css">
9
9
  </head>
10
10
  <body>
@@ -39,6 +39,7 @@ import type {
39
39
  SchemaHealthResponse,
40
40
  MaintenanceAction,
41
41
  MaintenanceActionResult,
42
+ DebugBundleResponse,
42
43
  ProviderCacheResponse,
43
44
  CacheStripesResponse,
44
45
  SetupStatusResponse,
@@ -284,6 +285,10 @@ export function postMaintenanceAction(
284
285
  );
285
286
  }
286
287
 
288
+ export function fetchDebugBundle(): Promise<DebugBundleResponse> {
289
+ return postJson<DebugBundleResponse>("/api/maintenance/gather-debug", {});
290
+ }
291
+
287
292
  // ─── Setup wizard (P0b) ────────────────────────────────────────────────────
288
293
 
289
294
  /** GET /api/setup-status — current embedder configuration. */
@@ -0,0 +1,161 @@
1
+ /**
2
+ * memory-map-layout.ts — Force-directed layout engine for MemoryMapTab (D3).
3
+ *
4
+ * Uses Coulomb repulsion + Hooke attraction + center gravity to arrange nodes.
5
+ * No external graph library. Mutates layout in place.
6
+ * Self-contained types: only needs id + edges from graph data.
7
+ */
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Types (self-contained — mirrors the subset used by the layout engine)
11
+ // ---------------------------------------------------------------------------
12
+
13
+ interface GraphNodeMin {
14
+ readonly id: string;
15
+ readonly nodeType: string;
16
+ readonly label: string;
17
+ readonly summaryTruncated: string;
18
+ readonly tokenEstimate: number;
19
+ readonly timestamp: number;
20
+ readonly dedupStatus: string | undefined;
21
+ readonly raptorLevel: number;
22
+ readonly topicSummary: string | undefined;
23
+ readonly decisionCount: number;
24
+ readonly textSnippet: string;
25
+ readonly sessionId: string;
26
+ }
27
+
28
+ interface GraphEdgeMin {
29
+ readonly source: string;
30
+ readonly target: string;
31
+ readonly weight: number;
32
+ readonly type: "temporal" | "semantic" | "raptor_parent";
33
+ }
34
+
35
+ export interface Position {
36
+ x: number;
37
+ y: number;
38
+ }
39
+
40
+ export interface LayoutNode extends GraphNodeMin {
41
+ pos: Position;
42
+ vx: number;
43
+ vy: number;
44
+ pinned: boolean;
45
+ }
46
+
47
+ export interface LayoutEdge {
48
+ source: number;
49
+ target: number;
50
+ weight: number;
51
+ type: GraphEdge["type"];
52
+ }
53
+
54
+ export interface Layout {
55
+ nodes: LayoutNode[];
56
+ edges: LayoutEdge[];
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Constants
61
+ // ---------------------------------------------------------------------------
62
+
63
+ const REPULSION = 5_000;
64
+ const ATTRACTION = 0.005;
65
+ const CENTER_GRAVITY = 0.01;
66
+ const DAMPING = 0.9;
67
+ const MIN_VELOCITY = 0.1;
68
+ const SPEED_LIMIT = 50;
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Build
72
+ // ---------------------------------------------------------------------------
73
+
74
+ /** Build initial layout from graph data. */
75
+ export function buildLayout(data: { nodes: GraphNodeMin[]; edges: GraphEdgeMin[] }): Layout {
76
+ const nodes: LayoutNode[] = data.nodes.map((n, i) => {
77
+ const angle = (2 * Math.PI * i) / Math.max(data.nodes.length, 1);
78
+ const radius = 200;
79
+ return {
80
+ ...n,
81
+ pos: { x: Math.cos(angle) * radius + 300, y: Math.sin(angle) * radius + 200 },
82
+ vx: 0,
83
+ vy: 0,
84
+ pinned: false,
85
+ };
86
+ });
87
+
88
+ const nodeIndex = new Map<string, number>();
89
+ nodes.forEach((n, i) => nodeIndex.set(n.id, i));
90
+
91
+ const edges: LayoutEdge[] = data.edges
92
+ .map((e) => {
93
+ const si = nodeIndex.get(e.source);
94
+ const ti = nodeIndex.get(e.target);
95
+ if (si === undefined || ti === undefined || si === ti) return null;
96
+ return { source: si, target: ti, weight: e.weight, type: e.type };
97
+ })
98
+ .filter((e): e is LayoutEdge => e !== null);
99
+
100
+ return { nodes, edges };
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Apply forces
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /** Apply one iteration of the force simulation. Mutates layout in place. */
108
+ export function applyForces(layout: Layout): void {
109
+ const { nodes, edges } = layout;
110
+ const n = nodes.length;
111
+ if (n === 0) return;
112
+
113
+ for (let i = 0; i < n; i++) {
114
+ if (nodes[i].pinned) continue;
115
+ let fx = 0;
116
+ let fy = 0;
117
+
118
+ // Coulomb repulsion
119
+ for (let j = 0; j < n; j++) {
120
+ if (i === j) continue;
121
+ const dx = nodes[i].pos.x - nodes[j].pos.x;
122
+ const dy = nodes[i].pos.y - nodes[j].pos.y;
123
+ const distSq = dx * dx + dy * dy || 1;
124
+ const force = REPULSION / distSq;
125
+ const dist = Math.sqrt(distSq);
126
+ fx += (dx / dist) * force;
127
+ fy += (dy / dist) * force;
128
+ }
129
+
130
+ // Hooke attraction along edges
131
+ for (const e of edges) {
132
+ let other: number;
133
+ if (e.source === i) other = e.target;
134
+ else if (e.target === i) other = e.source;
135
+ else continue;
136
+ const dx = nodes[other].pos.x - nodes[i].pos.x;
137
+ const dy = nodes[other].pos.y - nodes[i].pos.y;
138
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
139
+ fx += dx * ATTRACTION * e.weight;
140
+ fy += dy * ATTRACTION * e.weight;
141
+ }
142
+
143
+ // Center gravity
144
+ fx -= nodes[i].pos.x * CENTER_GRAVITY;
145
+ fy -= nodes[i].pos.y * CENTER_GRAVITY;
146
+
147
+ nodes[i].vx = (nodes[i].vx + fx) * DAMPING;
148
+ nodes[i].vy = (nodes[i].vy + fy) * DAMPING;
149
+
150
+ const speed = Math.sqrt(nodes[i].vx * nodes[i].vx + nodes[i].vy * nodes[i].vy);
151
+ if (speed > SPEED_LIMIT) {
152
+ nodes[i].vx = (nodes[i].vx / speed) * SPEED_LIMIT;
153
+ nodes[i].vy = (nodes[i].vy / speed) * SPEED_LIMIT;
154
+ }
155
+
156
+ if (speed > MIN_VELOCITY) {
157
+ nodes[i].pos.x += nodes[i].vx;
158
+ nodes[i].pos.y += nodes[i].vy;
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * memory-map-shapes.tsx — Node shape rendering utilities for MemoryMapTab (D3).
3
+ *
4
+ * Node shape encodes nodeType: checkpoint = filled circle, turn = hollow circle,
5
+ * turn-content = hollow+ring, memory = diamond.
6
+ * Delegate-shell pattern: thin rendering helpers, no state, no side effects.
7
+ */
8
+ import type React from "react";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Types
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export type NodeType = "checkpoint" | "turn" | "turn-content" | "memory";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Constants
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export const NODE_COLORS: Record<NodeType, string> = {
21
+ checkpoint: "#6366f1",
22
+ turn: "#58a6ff",
23
+ "turn-content": "#22c55e",
24
+ memory: "#f59e0b",
25
+ };
26
+
27
+ export const NODE_TYPE_LABELS: Record<NodeType, string> = {
28
+ checkpoint: "Checkpoint",
29
+ turn: "Turn",
30
+ "turn-content": "Turn Content",
31
+ memory: "Memory",
32
+ };
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Helpers
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /** SVG path for a diamond centered at (0,0) with given half-width/height. */
39
+ function diamondPath(hw: number, hh: number): string {
40
+ return `M 0,-${hh} L ${hw},0 L 0,${hh} L -${hw},0 Z`;
41
+ }
42
+
43
+ function nodeOpacity(dedupStatus: string | undefined): number {
44
+ if (!dedupStatus || dedupStatus === "active") return 1;
45
+ return 0.5;
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Render function
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /** Render an SVG shape for a node based on its nodeType. */
53
+ export function renderNodeShape(
54
+ cx: number,
55
+ cy: number,
56
+ radius: number,
57
+ nodeType: NodeType,
58
+ isSelected: boolean,
59
+ isFiltered: boolean,
60
+ dedupStatus: string | undefined,
61
+ ): React.ReactElement {
62
+ const fill = isFiltered ? NODE_COLORS[nodeType] ?? "#6366f1" : "#d1d5db";
63
+ const strokeColor = isSelected ? "#ef4444" : "none";
64
+ const opacity = nodeOpacity(dedupStatus);
65
+
66
+ switch (nodeType) {
67
+ case "checkpoint":
68
+ return (
69
+ <circle
70
+ cx={cx}
71
+ cy={cy}
72
+ r={isSelected ? radius + 3 : radius}
73
+ fill={fill}
74
+ opacity={opacity}
75
+ stroke={strokeColor}
76
+ strokeWidth={isSelected ? 2 : 0}
77
+ />
78
+ );
79
+ case "turn":
80
+ return (
81
+ <circle
82
+ cx={cx}
83
+ cy={cy}
84
+ r={isSelected ? radius + 3 : radius}
85
+ fill="none"
86
+ stroke={fill}
87
+ strokeWidth={2.5}
88
+ opacity={opacity}
89
+ />
90
+ );
91
+ case "turn-content":
92
+ return (
93
+ <g>
94
+ <circle
95
+ cx={cx}
96
+ cy={cy}
97
+ r={isSelected ? radius + 3 : radius}
98
+ fill="none"
99
+ stroke={fill}
100
+ strokeWidth={2.5}
101
+ opacity={opacity}
102
+ />
103
+ <circle
104
+ cx={cx}
105
+ cy={cy}
106
+ r={Math.max(3, radius * 0.3)}
107
+ fill={fill}
108
+ opacity={opacity}
109
+ />
110
+ </g>
111
+ );
112
+ case "memory":
113
+ const hw = radius * 0.85;
114
+ const hh = radius * 0.85;
115
+ return (
116
+ <path
117
+ d={diamondPath(hw, hh)}
118
+ transform={`translate(${cx}, ${cy})`}
119
+ fill={fill}
120
+ opacity={opacity}
121
+ stroke={strokeColor}
122
+ strokeWidth={isSelected ? 2 : 0}
123
+ />
124
+ );
125
+ }
126
+ }
@@ -11,8 +11,7 @@
11
11
  * 5s polling on provider cache and activity snapshot; 15s on stripes + trend.
12
12
  */
13
13
 
14
- import type React from "react";
15
- import { useCallback } from "react";
14
+ import { useState, useCallback } from "react";
16
15
  import type {
17
16
  SnapshotResponse,
18
17
  ProviderCacheResponse,
@@ -33,6 +32,7 @@ import { StripeDistributionCard } from "../components/StripeDistributionCard";
33
32
  import { CacheHitRateTrendCard } from "../components/CacheHitRateTrendCard";
34
33
 
35
34
  export default function CacheTab(): React.ReactElement {
35
+ const [infoExpanded, setInfoExpanded] = useState(false);
36
36
  /* --- Provider prompt cache (5s poll) --- */
37
37
  const {
38
38
  data: providerCache,
@@ -75,6 +75,33 @@ export default function CacheTab(): React.ReactElement {
75
75
 
76
76
  return (
77
77
  <div className="cache-tab">
78
+ <div className="card-section">
79
+ <button
80
+ type="button"
81
+ className="info-toggle"
82
+ onClick={() => setInfoExpanded(!infoExpanded)}
83
+ >
84
+ {infoExpanded ? "Hide Guidance" : "Show Cache-Friendly Prompt Ordering Guidance"}
85
+ </button>
86
+ {infoExpanded && (
87
+ <div className="info-panel">
88
+ <h3>Cache-Friendly Prompt Ordering</h3>
89
+ <ul>
90
+ <li>Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix.</li>
91
+ <li>Tool results and volatile content are automatically moved to the tail by message separation (<code>MEGACOMPACT_MESSAGE_SEPARATION</code>) so they don't invalidate the cache prefix.</li>
92
+ <li>Cache striping (<code>MEGACOMPACT_CACHE_STRIPING</code>) further orders stable context by a stability score so the most durable chunks lead.</li>
93
+ <li>Avoid inserting new instructions mid-conversation — prepend them instead.</li>
94
+ </ul>
95
+ {snapshot && (
96
+ <div className="cache-status-indicator">
97
+ <strong>Status:</strong>{" "}
98
+ {snapshot.config.messageSeparation ? "Separation Enabled" : "Separation Disabled (Check env)"} |{" "}
99
+ {snapshot.config.cacheStriping ? "Striping Enabled" : "Striping Disabled (Check env)"}
100
+ </div>
101
+ )}
102
+ </div>
103
+ )}
104
+ </div>
78
105
  {/* ==============================================================
79
106
  Section 1 -- Provider Prompt Cache
80
107
  ============================================================== */}
@@ -16,12 +16,14 @@ import type {
16
16
  SchemaHealthResponse,
17
17
  MaintenanceAction,
18
18
  MaintenanceActionResult,
19
+ DebugBundleResponse,
19
20
  } from "@contracts";
20
21
  import { useApi } from "../hooks/useApi";
21
22
  import {
22
23
  fetchDbStats,
23
24
  fetchSchemaHealth,
24
25
  postMaintenanceAction,
26
+ fetchDebugBundle,
25
27
  } from "../api/client";
26
28
 
27
29
  // ---------------------------------------------------------------------------
@@ -419,6 +421,104 @@ function ActionsCard(): React.ReactElement {
419
421
  );
420
422
  }
421
423
 
424
+ // ---------------------------------------------------------------------------
425
+ // Debug bundle card — gather diagnostic info for bug reports
426
+ // ---------------------------------------------------------------------------
427
+
428
+ function DebugBundleCard(): React.ReactElement {
429
+ const [bundle, setBundle] = useState<DebugBundleResponse | null>(null);
430
+ const [loading, setLoading] = useState(false);
431
+ const [error, setError] = useState<string | null>(null);
432
+ const [showJson, setShowJson] = useState(false);
433
+
434
+ const gather = useCallback(async () => {
435
+ setLoading(true);
436
+ setError(null);
437
+ try {
438
+ const res = await fetchDebugBundle();
439
+ setBundle(res);
440
+ setShowJson(true);
441
+ } catch (err) {
442
+ setError(err instanceof Error ? err.message : String(err));
443
+ } finally {
444
+ setLoading(false);
445
+ }
446
+ }, []);
447
+
448
+ const copyToClipboard = useCallback(() => {
449
+ if (!bundle) return;
450
+ const text = JSON.stringify(bundle, null, 2);
451
+ void navigator.clipboard.writeText(text).catch(() => {});
452
+ }, [bundle]);
453
+
454
+ const downloadJson = useCallback(() => {
455
+ if (!bundle) return;
456
+ const text = JSON.stringify(bundle, null, 2);
457
+ const blob = new Blob([text], { type: "application/json" });
458
+ const url = URL.createObjectURL(blob);
459
+ const a = document.createElement("a");
460
+ a.href = url;
461
+ a.download = `mega-compact-debug-bundle-${bundle.builtAt}.json`;
462
+ a.click();
463
+ URL.revokeObjectURL(url);
464
+ }, [bundle]);
465
+
466
+ const criticalCount = bundle?.criticalEvents.length ?? 0;
467
+
468
+ return (
469
+ <div className="card">
470
+ <div className="card-header">
471
+ Debug Bundle
472
+ {criticalCount > 0 && (
473
+ <span className="text-warn" style={{ marginLeft: "1em" }}>
474
+ {criticalCount} critical event{criticalCount !== 1 ? "s" : ""} found
475
+ </span>
476
+ )}
477
+ </div>
478
+ <div className="card-body">
479
+ <p className="text-muted" style={{ marginBottom: "0.75em" }}>
480
+ Gather a diagnostic bundle (recent events, config flags, schema health,
481
+ store stats) to attach to a bug report. Critical/compaction events are
482
+ highlighted.
483
+ </p>
484
+ <button onClick={gather} disabled={loading} style={{ marginRight: "0.5em" }}>
485
+ {loading ? "Gathering…" : "Gather Debug Logs"}
486
+ </button>
487
+ {error && <div className="text-error">{error}</div>}
488
+ {bundle && showJson && (
489
+ <>
490
+ <div style={{ marginTop: "0.75em", marginBottom: "0.5em" }}>
491
+ <button onClick={copyToClipboard} style={{ marginRight: "0.5em" }}>
492
+ Copy to clipboard
493
+ </button>
494
+ <button onClick={downloadJson}>Download JSON</button>
495
+ <button
496
+ onClick={() => setShowJson((v) => !v)}
497
+ style={{ marginLeft: "0.5em" }}
498
+ >
499
+ {showJson ? "Hide" : "Show"}
500
+ </button>
501
+ </div>
502
+ {criticalCount > 0 && (
503
+ <div className="stat-section">
504
+ <div className="stat-label text-warn">
505
+ Critical events ({criticalCount})
506
+ </div>
507
+ <pre className="text-mono" style={{ maxHeight: "12em", overflow: "auto" }}>
508
+ {JSON.stringify(bundle.criticalEvents, null, 2)}
509
+ </pre>
510
+ </div>
511
+ )}
512
+ <pre className="text-mono" style={{ maxHeight: "24em", overflow: "auto" }}>
513
+ {JSON.stringify(bundle, null, 2)}
514
+ </pre>
515
+ </>
516
+ )}
517
+ </div>
518
+ </div>
519
+ );
520
+ }
521
+
422
522
  // ---------------------------------------------------------------------------
423
523
  // Main tab component
424
524
  // ---------------------------------------------------------------------------
@@ -453,6 +553,7 @@ export default function MaintenanceTab(): React.ReactElement {
453
553
  <DbStatsCard data={dbStats} loading={statsLoading} error={statsError} />
454
554
  </div>
455
555
  <ActionsCard />
556
+ <DebugBundleCard />
456
557
  </div>
457
558
  );
458
559
  }