pi-mega-compact 0.11.3 → 0.11.5

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 (307) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +19 -0
  2. package/dist/extensions/dashboard-server/api-contracts/memory.js +8 -0
  3. package/dist/extensions/dashboard-server/api-contracts/provider-cache.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/setup.js +10 -0
  5. package/dist/extensions/dashboard-server/index-reader.js +6 -2
  6. package/dist/extensions/dashboard-server/routes-cache.js +115 -55
  7. package/dist/extensions/dashboard-server/routes-memory.js +51 -0
  8. package/dist/extensions/dashboard-server/routes-repo.js +2 -2
  9. package/dist/extensions/dashboard-server/routes-setup.js +162 -0
  10. package/dist/extensions/dashboard-server/routes.js +2 -0
  11. package/dist/extensions/dashboard-server/server.js +7 -1
  12. package/dist/extensions/mega-commands.js +189 -1
  13. package/dist/extensions/mega-config.js +3 -0
  14. package/dist/extensions/mega-events/agent-handlers.js +41 -6
  15. package/dist/extensions/mega-events/compact-handlers.js +1 -0
  16. package/dist/extensions/mega-events/context-handler.js +37 -92
  17. package/dist/extensions/mega-events/context-hwm.js +51 -0
  18. package/dist/extensions/mega-events/error-classifier.js +118 -128
  19. package/dist/extensions/mega-events/mirror-append.js +107 -0
  20. package/dist/extensions/mega-events/outage-advisor.js +59 -0
  21. package/dist/extensions/mega-events/perf-handler.js +51 -1
  22. package/dist/extensions/mega-events/recall-tail.js +59 -0
  23. package/dist/extensions/mega-events/session-handlers.js +15 -8
  24. package/dist/extensions/mega-runtime/reset-runtime.js +6 -0
  25. package/dist/extensions/mega-runtime/runtime.js +7 -0
  26. package/dist/src/embedder.js +41 -2
  27. package/dist/src/memoryStats.js +75 -0
  28. package/dist/src/prefix-break.js +43 -0
  29. package/dist/src/pricing.js +1 -1
  30. package/dist/src/store/sqlite/perf-samples.js +144 -16
  31. package/dist/src/store/turns/memory-store.js +8 -0
  32. package/dist/src/store/turns/sqlite-store.js +14 -3
  33. package/dist/src/store/turns/types.js +18 -1
  34. package/dist/src/store/vectorIndex.js +26 -5
  35. package/extensions/dashboard-client/dist/assets/{AchievementTiles-vEo2MtE2.js → AchievementTiles-BoL7qBOJ.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{AchievementTiles-vEo2MtE2.js.map → AchievementTiles-BoL7qBOJ.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/AchievementsTab-BFDE3HYc.js +2 -0
  38. package/extensions/dashboard-client/dist/assets/{AchievementsTab-DjxfYrDc.js.map → AchievementsTab-BFDE3HYc.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/CacheTab-eKVc-NV1.js +2 -0
  40. package/extensions/dashboard-client/dist/assets/CacheTab-eKVc-NV1.js.map +1 -0
  41. package/extensions/dashboard-client/dist/assets/{ConfigTab-Cm6ymbmx.js → ConfigTab-DDFE-ckA.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{ConfigTab-Cm6ymbmx.js.map → ConfigTab-DDFE-ckA.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{EventsTab-DXWl_YzI.js → EventsTab-B6UxZ7PM.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{EventsTab-DXWl_YzI.js.map → EventsTab-B6UxZ7PM.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{GameTab-BLDudcO5.js → GameTab-DehUX-KE.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{GameTab-BLDudcO5.js.map → GameTab-DehUX-KE.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DLkKCztn.js → MaintenanceTab-0GWRt4wn.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DLkKCztn.js.map → MaintenanceTab-0GWRt4wn.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{MetricsTab-Cq4u-rZF.js → MetricsTab-C3rwMVsN.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{MetricsTab-Cq4u-rZF.js.map → MetricsTab-C3rwMVsN.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/OverviewTab-39O9_x5j.js +2 -0
  52. package/extensions/dashboard-client/dist/assets/{OverviewTab-D4kGvsiQ.js.map → OverviewTab-39O9_x5j.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/ReposTab-Cfj6Q5sH.js +2 -0
  54. package/extensions/dashboard-client/dist/assets/ReposTab-Cfj6Q5sH.js.map +1 -0
  55. package/extensions/dashboard-client/dist/assets/{SessionsTab-DZC2-GCn.js → SessionsTab-DXDTCi3r.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{SessionsTab-DZC2-GCn.js.map → SessionsTab-DXDTCi3r.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/SetupTab-mMlfunJa.js +2 -0
  58. package/extensions/dashboard-client/dist/assets/SetupTab-mMlfunJa.js.map +1 -0
  59. package/extensions/dashboard-client/dist/assets/TimeSavedCard-Cw1dJ4e9.js +2 -0
  60. package/extensions/dashboard-client/dist/assets/TimeSavedCard-Cw1dJ4e9.js.map +1 -0
  61. package/extensions/dashboard-client/dist/assets/{TopicsTab-01G71hv1.js → TopicsTab-BAw972BV.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{TopicsTab-01G71hv1.js.map → TopicsTab-BAw972BV.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{TurnsTab-DLRg0LZT.js → TurnsTab-Cf5mGIdl.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{TurnsTab-DLRg0LZT.js.map → TurnsTab-Cf5mGIdl.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/format-COjDIVGj.js +2 -0
  66. package/extensions/dashboard-client/dist/assets/format-COjDIVGj.js.map +1 -0
  67. package/extensions/dashboard-client/dist/assets/{index-BX0bcgLu.js → index-4gQ7auad.js} +10 -10
  68. package/extensions/dashboard-client/dist/assets/index-4gQ7auad.js.map +1 -0
  69. package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js +2 -0
  70. package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js.map +1 -0
  71. package/extensions/dashboard-client/dist/assets/{useSSE-BBYt5UPB.js → useSSE-DNgMuyP6.js} +2 -2
  72. package/extensions/dashboard-client/dist/assets/{useSSE-BBYt5UPB.js.map → useSSE-DNgMuyP6.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/index.html +1 -1
  74. package/extensions/dashboard-client/src/App.tsx +4 -0
  75. package/extensions/dashboard-client/src/api/client.ts +14 -0
  76. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +21 -12
  77. package/extensions/dashboard-client/src/components/ProviderCacheCard.tsx +56 -0
  78. package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +38 -57
  79. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +1 -0
  80. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +355 -0
  81. package/extensions/dashboard-client/tsconfig.json +3 -2
  82. package/extensions/dashboard-client/vite.config.ts +2 -0
  83. package/extensions/dashboard-server/api-contracts/endpoints.ts +36 -3
  84. package/extensions/dashboard-server/api-contracts/index.ts +7 -0
  85. package/extensions/dashboard-server/api-contracts/memory.ts +27 -0
  86. package/extensions/dashboard-server/api-contracts/multi-repo.ts +231 -231
  87. package/extensions/dashboard-server/api-contracts/provider-cache.ts +37 -0
  88. package/extensions/dashboard-server/api-contracts/setup.ts +58 -0
  89. package/extensions/dashboard-server/index-reader.ts +138 -121
  90. package/extensions/dashboard-server/routes-cache.ts +161 -85
  91. package/extensions/dashboard-server/routes-memory.ts +63 -0
  92. package/extensions/dashboard-server/routes-repo.ts +36 -36
  93. package/extensions/dashboard-server/routes-setup.ts +181 -0
  94. package/extensions/dashboard-server/routes.ts +2 -0
  95. package/extensions/dashboard-server/server.ts +6 -0
  96. package/extensions/mega-commands.ts +211 -1
  97. package/extensions/mega-config.ts +18 -0
  98. package/extensions/mega-events/agent-handlers.ts +44 -5
  99. package/extensions/mega-events/compact-handlers.ts +1 -0
  100. package/extensions/mega-events/context-handler.ts +45 -110
  101. package/extensions/mega-events/context-hwm.ts +85 -0
  102. package/extensions/mega-events/error-classifier.ts +156 -124
  103. package/extensions/mega-events/mirror-append.ts +155 -0
  104. package/extensions/mega-events/outage-advisor.ts +79 -0
  105. package/extensions/mega-events/perf-handler.ts +79 -5
  106. package/extensions/mega-events/recall-tail.ts +88 -0
  107. package/extensions/mega-events/session-handlers.ts +14 -7
  108. package/extensions/mega-runtime/helpers.ts +14 -0
  109. package/extensions/mega-runtime/reset-runtime.ts +6 -0
  110. package/extensions/mega-runtime/runtime.ts +7 -0
  111. package/package.json +5 -2
  112. package/src/embedder.ts +42 -2
  113. package/src/memoryStats.ts +107 -0
  114. package/src/prefix-break.ts +83 -0
  115. package/src/pricing.ts +2 -2
  116. package/src/store/sqlite/perf-samples.ts +215 -22
  117. package/src/store/turns/memory-store.ts +10 -1
  118. package/src/store/turns/sqlite-store.ts +27 -15
  119. package/src/store/turns/types.ts +21 -0
  120. package/src/store/vectorIndex.ts +33 -4
  121. package/dist/extensions/conflict-scan.test.js +0 -115
  122. package/dist/extensions/dashboard-server/api-contracts.test.js +0 -884
  123. package/dist/extensions/dashboard-server/html.test.js +0 -52
  124. package/dist/extensions/dashboard-server/perf-server.test.js +0 -80
  125. package/dist/extensions/dashboard-server/routes-cache.test.js +0 -248
  126. package/dist/extensions/dashboard-server/server.test.js +0 -120
  127. package/dist/extensions/dashboard-server-s32.test.js +0 -181
  128. package/dist/extensions/dashboard-server.test.js +0 -339
  129. package/dist/extensions/mega-cache-replay.test.js +0 -400
  130. package/dist/extensions/mega-compact-s38.test.js +0 -820
  131. package/dist/extensions/mega-compact.test.js +0 -1112
  132. package/dist/extensions/mega-events.test.js +0 -47
  133. package/dist/extensions/mega-game-cmds.test.js +0 -163
  134. package/dist/extensions/mega-runtime/snapshot.test.js +0 -127
  135. package/dist/extensions/mega-runtime/state.test.js +0 -266
  136. package/dist/extensions/mega-runtime/widget.test.js +0 -343
  137. package/dist/extensions/mega-shutdown-widget.test.js +0 -121
  138. package/dist/extensions/mega-teamrun.test.js +0 -156
  139. package/dist/extensions/mega-trim.test.js +0 -58
  140. package/dist/extensions/mega-turn-store.test.js +0 -68
  141. package/dist/src/boundary.test.js +0 -170
  142. package/dist/src/compact.test.js +0 -78
  143. package/dist/src/config/themes.test.js +0 -94
  144. package/dist/src/dedup/dedup.test.js +0 -41
  145. package/dist/src/dedup/l1.test.js +0 -50
  146. package/dist/src/dedup/raptor/buildHistory.test.js +0 -292
  147. package/dist/src/dedup/raptor/multilevel-serve.test.js +0 -229
  148. package/dist/src/dedup/raptor/multilevel.test.js +0 -238
  149. package/dist/src/dedup/raptor/promote.test.js +0 -69
  150. package/dist/src/dedup/raptor/raptor.test.js +0 -248
  151. package/dist/src/dedup/raptor/retrieval.test.js +0 -95
  152. package/dist/src/dedup/raptor/serve-gate.test.js +0 -298
  153. package/dist/src/dedup/sprint12.test.js +0 -219
  154. package/dist/src/dedup-engine.test.js +0 -472
  155. package/dist/src/driftDetection.test.js +0 -87
  156. package/dist/src/e2e.test.js +0 -698
  157. package/dist/src/engine.test.js +0 -111
  158. package/dist/src/extractive.test.js +0 -147
  159. package/dist/src/fork.test.js +0 -81
  160. package/dist/src/game/scoring.test.js +0 -98
  161. package/dist/src/httpEmbedder.test.js +0 -277
  162. package/dist/src/importance.test.js +0 -372
  163. package/dist/src/intent.test.js +0 -78
  164. package/dist/src/log.test.js +0 -42
  165. package/dist/src/mechanical-fix.test.js +0 -65
  166. package/dist/src/memory.test.js +0 -70
  167. package/dist/src/memoryConsolidate.test.js +0 -38
  168. package/dist/src/memoryOps.test.js +0 -129
  169. package/dist/src/memoryRecall.test.js +0 -171
  170. package/dist/src/memoryRoundtrip.test.js +0 -137
  171. package/dist/src/metrics/turns.test.js +0 -152
  172. package/dist/src/mirror/mirror.test.js +0 -185
  173. package/dist/src/raptor-inject-summaries.test.js +0 -162
  174. package/dist/src/ratio.bench.test.js +0 -897
  175. package/dist/src/recall.integration.test.js +0 -77
  176. package/dist/src/recall.test.js +0 -379
  177. package/dist/src/sprint14.test.js +0 -219
  178. package/dist/src/sprint4x-rag-verification.test.js +0 -93
  179. package/dist/src/store/compression.test.js +0 -139
  180. package/dist/src/store/memoryIndex.test.js +0 -51
  181. package/dist/src/store/migrate.test.js +0 -139
  182. package/dist/src/store/pgOpenGuard.test.js +0 -74
  183. package/dist/src/store/phase01.test.js +0 -81
  184. package/dist/src/store/phase04.test.js +0 -54
  185. package/dist/src/store/sprint10.test.js +0 -186
  186. package/dist/src/store/sqlite/game-achievements.test.js +0 -67
  187. package/dist/src/store/sqlite/game-scores.test.js +0 -106
  188. package/dist/src/store/sqlite/game-state.test.js +0 -76
  189. package/dist/src/store/sqlite/global-index.test.js +0 -136
  190. package/dist/src/store/sqlite/mechanical-fix.test.js +0 -146
  191. package/dist/src/store/sqlite/perf-samples.test.js +0 -146
  192. package/dist/src/store/sqlite/raptor.test.js +0 -106
  193. package/dist/src/store/sqlite/turns.test.js +0 -183
  194. package/dist/src/store/sqlite.cachehit.test.js +0 -55
  195. package/dist/src/store/sqlite.dbmaint.test.js +0 -158
  196. package/dist/src/store/sqlite.dbmirror.test.js +0 -184
  197. package/dist/src/store/turns/contract-compliance.test.js +0 -428
  198. package/dist/src/store/turns/memory-store.test.js +0 -9
  199. package/dist/src/store/turns/migrations.test.js +0 -138
  200. package/dist/src/store/turns/sqlite-store.test.js +0 -144
  201. package/dist/src/store/vectorIndex.test.js +0 -123
  202. package/dist/src/store.test.js +0 -161
  203. package/dist/src/supersede.test.js +0 -36
  204. package/dist/src/topics/cluster.test.js +0 -150
  205. package/dist/src/topics/store.test.js +0 -117
  206. package/dist/src/types.test.js +0 -9
  207. package/dist/src/vector-search-cache.test.js +0 -157
  208. package/dist/src/vectorStore.test.js +0 -479
  209. package/dist/src/wiki.test.js +0 -171
  210. package/extensions/conflict-scan.test.ts +0 -129
  211. package/extensions/dashboard-client/dist/assets/AchievementsTab-DjxfYrDc.js +0 -2
  212. package/extensions/dashboard-client/dist/assets/CacheTab-Dnpv94wq.js +0 -2
  213. package/extensions/dashboard-client/dist/assets/CacheTab-Dnpv94wq.js.map +0 -1
  214. package/extensions/dashboard-client/dist/assets/OverviewTab-D4kGvsiQ.js +0 -2
  215. package/extensions/dashboard-client/dist/assets/ReposTab-C0Wnrf2u.js +0 -2
  216. package/extensions/dashboard-client/dist/assets/ReposTab-C0Wnrf2u.js.map +0 -1
  217. package/extensions/dashboard-client/dist/assets/TimeSavedCard-UqljSyP-.js +0 -2
  218. package/extensions/dashboard-client/dist/assets/TimeSavedCard-UqljSyP-.js.map +0 -1
  219. package/extensions/dashboard-client/dist/assets/index-BX0bcgLu.js.map +0 -1
  220. package/extensions/dashboard-server/api-contracts.test.ts +0 -1065
  221. package/extensions/dashboard-server/html.test.ts +0 -62
  222. package/extensions/dashboard-server/perf-server.test.ts +0 -101
  223. package/extensions/dashboard-server/routes-cache.test.ts +0 -302
  224. package/extensions/dashboard-server/server.test.ts +0 -131
  225. package/extensions/dashboard-server-s32.test.ts +0 -195
  226. package/extensions/dashboard-server.test.ts +0 -382
  227. package/extensions/mega-cache-replay.test.ts +0 -526
  228. package/extensions/mega-compact-s38.test.ts +0 -872
  229. package/extensions/mega-compact.test.ts +0 -1509
  230. package/extensions/mega-events.test.ts +0 -55
  231. package/extensions/mega-game-cmds.test.ts +0 -197
  232. package/extensions/mega-runtime/snapshot.test.ts +0 -139
  233. package/extensions/mega-runtime/state.test.ts +0 -262
  234. package/extensions/mega-runtime/widget.test.ts +0 -468
  235. package/extensions/mega-shutdown-widget.test.ts +0 -141
  236. package/extensions/mega-teamrun.test.ts +0 -176
  237. package/extensions/mega-trim.test.ts +0 -64
  238. package/extensions/mega-turn-store.test.ts +0 -108
  239. package/src/boundary.test.ts +0 -187
  240. package/src/compact.test.ts +0 -99
  241. package/src/config/themes.test.ts +0 -116
  242. package/src/dedup/dedup.test.ts +0 -46
  243. package/src/dedup/l1.test.ts +0 -57
  244. package/src/dedup/raptor/buildHistory.test.ts +0 -353
  245. package/src/dedup/raptor/multilevel-serve.test.ts +0 -273
  246. package/src/dedup/raptor/multilevel.test.ts +0 -325
  247. package/src/dedup/raptor/promote.test.ts +0 -82
  248. package/src/dedup/raptor/raptor.test.ts +0 -297
  249. package/src/dedup/raptor/retrieval.test.ts +0 -118
  250. package/src/dedup/raptor/serve-gate.test.ts +0 -348
  251. package/src/dedup/sprint12.test.ts +0 -242
  252. package/src/dedup-engine.test.ts +0 -670
  253. package/src/driftDetection.test.ts +0 -100
  254. package/src/e2e.test.ts +0 -843
  255. package/src/engine.test.ts +0 -123
  256. package/src/extractive.test.ts +0 -174
  257. package/src/fork.test.ts +0 -94
  258. package/src/game/scoring.test.ts +0 -103
  259. package/src/httpEmbedder.test.ts +0 -286
  260. package/src/importance.test.ts +0 -538
  261. package/src/intent.test.ts +0 -87
  262. package/src/log.test.ts +0 -47
  263. package/src/mechanical-fix.test.ts +0 -70
  264. package/src/memory.test.ts +0 -92
  265. package/src/memoryConsolidate.test.ts +0 -47
  266. package/src/memoryOps.test.ts +0 -148
  267. package/src/memoryRecall.test.ts +0 -194
  268. package/src/memoryRoundtrip.test.ts +0 -155
  269. package/src/metrics/turns.test.ts +0 -165
  270. package/src/mirror/mirror.test.ts +0 -240
  271. package/src/raptor-inject-summaries.test.ts +0 -228
  272. package/src/ratio.bench.test.ts +0 -1316
  273. package/src/recall.integration.test.ts +0 -96
  274. package/src/recall.test.ts +0 -467
  275. package/src/sprint14.test.ts +0 -245
  276. package/src/sprint4x-rag-verification.test.ts +0 -119
  277. package/src/store/compression.test.ts +0 -168
  278. package/src/store/memoryIndex.test.ts +0 -61
  279. package/src/store/migrate.test.ts +0 -158
  280. package/src/store/pgOpenGuard.test.ts +0 -89
  281. package/src/store/phase01.test.ts +0 -88
  282. package/src/store/phase04.test.ts +0 -58
  283. package/src/store/sprint10.test.ts +0 -214
  284. package/src/store/sqlite/game-achievements.test.ts +0 -80
  285. package/src/store/sqlite/game-scores.test.ts +0 -132
  286. package/src/store/sqlite/game-state.test.ts +0 -89
  287. package/src/store/sqlite/global-index.test.ts +0 -222
  288. package/src/store/sqlite/mechanical-fix.test.ts +0 -162
  289. package/src/store/sqlite/perf-samples.test.ts +0 -169
  290. package/src/store/sqlite/raptor.test.ts +0 -139
  291. package/src/store/sqlite/turns.test.ts +0 -218
  292. package/src/store/sqlite.cachehit.test.ts +0 -68
  293. package/src/store/sqlite.dbmaint.test.ts +0 -193
  294. package/src/store/sqlite.dbmirror.test.ts +0 -229
  295. package/src/store/turns/contract-compliance.test.ts +0 -562
  296. package/src/store/turns/memory-store.test.ts +0 -15
  297. package/src/store/turns/migrations.test.ts +0 -161
  298. package/src/store/turns/sqlite-store.test.ts +0 -161
  299. package/src/store/vectorIndex.test.ts +0 -140
  300. package/src/store.test.ts +0 -191
  301. package/src/supersede.test.ts +0 -42
  302. package/src/topics/cluster.test.ts +0 -176
  303. package/src/topics/store.test.ts +0 -132
  304. package/src/types.test.ts +0 -10
  305. package/src/vector-search-cache.test.ts +0 -190
  306. package/src/vectorStore.test.ts +0 -533
  307. package/src/wiki.test.ts +0 -217
@@ -16,138 +16,155 @@ import { DatabaseSync } from "node:sqlite";
16
16
  import type { IndexRepo, IndexIndex } from "./types.js";
17
17
 
18
18
  export function getIndexDir(): string {
19
- const override = process.env.MEGACOMPACT_INDEX_DIR;
20
- if (override && override.trim() !== "") return override;
21
- try {
22
- return join(homedir(), ".mega-compact-index");
23
- } catch {
24
- return join("/tmp", ".mega-compact-index");
25
- }
19
+ const override = process.env.MEGACOMPACT_INDEX_DIR;
20
+ if (override && override.trim() !== "") return override;
21
+ try {
22
+ return join(homedir(), ".mega-compact-index");
23
+ } catch {
24
+ return join("/tmp", ".mega-compact-index");
25
+ }
26
26
  }
27
27
 
28
28
  /** Read the machine-wide repo registry from SQLite (read-only, single shot). */
29
29
  export function readIndex(): IndexIndex | null {
30
- const indexPath = join(getIndexDir(), "index.sqlite");
31
- if (!existsSync(indexPath)) return null;
32
- let db: DatabaseSync | undefined;
33
- try {
34
- // Read-only + immutable WAL so a concurrent writer's WAL never blocks us.
35
- db = new DatabaseSync(indexPath, { readOnly: true });
36
- db.exec("PRAGMA journal_mode = WAL");
37
- const rows = db
38
- .prepare("SELECT * FROM repo_registry ORDER BY last_seen DESC")
39
- .all() as Record<string, unknown>[];
40
- const mapped: IndexRepo[] = rows.map((r) => ({
41
- repoRoot: String(r.repo_root ?? ""),
42
- displayName: String(r.display_name ?? ""),
43
- stateDir: String(r.state_dir ?? ""),
44
- checkpointCount: Number(r.checkpoint_count ?? 0),
45
- tokensSaved: Number(r.tokens_saved ?? 0),
46
- compressedOriginalBytes: Number(r.compressed_original_bytes ?? 0),
47
- lastCompactedAt: (r.last_compacted_at as number | null) ?? null,
48
- provider: (r.provider as string | null) ?? null,
49
- providerName: (r.provider_name as string | null) ?? null,
50
- modelName: (r.model_name as string | null) ?? null,
51
- inputRate: (r.input_rate as number | null) ?? null,
52
- outputRate: (r.output_rate as number | null) ?? null,
53
- lastSeen: Number(r.last_seen ?? 0),
54
- // Defaults — enriched below from each repo's own store.
55
- tokensKept: 0,
56
- tokensDropped: 0,
57
- sessions: 0,
58
- contextWindow: null,
59
- maxTokens: null,
60
- reasoning: null,
61
- providerCachePct: null,
62
- providerCacheRead: null,
63
- providerCacheWrite: null,
64
- }));
65
- // Enrich each repo with per-store token + model detail read directly via
66
- // node:sqlite (same zero-dependency invariant as readIndex; no store graph
67
- // import). Best-effort: a missing/corrupt store degrades to the defaults
68
- // above so the dashboard never fails to render.
69
- for (const repo of mapped) {
70
- try {
71
- const storePath = join(repo.stateDir, "sqlite.db");
72
- if (existsSync(storePath)) {
73
- const sdb = new DatabaseSync(storePath, { readOnly: true });
74
- try {
75
- const tok = sdb
76
- .prepare(
77
- `SELECT COALESCE(SUM(token_estimate),0) AS kept,
30
+ const indexPath = join(getIndexDir(), "index.sqlite");
31
+ if (!existsSync(indexPath)) return null;
32
+ let db: DatabaseSync | undefined;
33
+ try {
34
+ // Read-only + immutable WAL so a concurrent writer's WAL never blocks us.
35
+ db = new DatabaseSync(indexPath, { readOnly: true });
36
+ db.exec("PRAGMA journal_mode = WAL");
37
+ const rows = db
38
+ .prepare("SELECT * FROM repo_registry ORDER BY last_seen DESC")
39
+ .all() as Record<string, unknown>[];
40
+ const mapped: IndexRepo[] = rows.map((r) => ({
41
+ repoRoot: String(r.repo_root ?? ""),
42
+ displayName: String(r.display_name ?? ""),
43
+ stateDir: String(r.state_dir ?? ""),
44
+ checkpointCount: Number(r.checkpoint_count ?? 0),
45
+ tokensSaved: Number(r.tokens_saved ?? 0),
46
+ compressedOriginalBytes: Number(r.compressed_original_bytes ?? 0),
47
+ lastCompactedAt: (r.last_compacted_at as number | null) ?? null,
48
+ provider: (r.provider as string | null) ?? null,
49
+ providerName: (r.provider_name as string | null) ?? null,
50
+ modelName: (r.model_name as string | null) ?? null,
51
+ inputRate: (r.input_rate as number | null) ?? null,
52
+ outputRate: (r.output_rate as number | null) ?? null,
53
+ lastSeen: Number(r.last_seen ?? 0),
54
+ // Defaults — enriched below from each repo's own store.
55
+ tokensKept: 0,
56
+ tokensDropped: 0,
57
+ sessions: 0,
58
+ contextWindow: null,
59
+ maxTokens: null,
60
+ reasoning: null,
61
+ providerCachePct: null,
62
+ providerCacheRead: null,
63
+ providerCacheWrite: null,
64
+ }));
65
+ // Enrich each repo with per-store token + model detail read directly via
66
+ // node:sqlite (same zero-dependency invariant as readIndex; no store graph
67
+ // import). Best-effort: a missing/corrupt store degrades to the defaults
68
+ // above so the dashboard never fails to render.
69
+ for (const repo of mapped) {
70
+ try {
71
+ const storePath = join(repo.stateDir, "sqlite.db");
72
+ if (existsSync(storePath)) {
73
+ const sdb = new DatabaseSync(storePath, { readOnly: true });
74
+ try {
75
+ const tok = sdb
76
+ .prepare(
77
+ `SELECT COALESCE(SUM(token_estimate),0) AS kept,
78
78
  COALESCE(SUM(original_token_estimate),0) AS dropped,
79
79
  COUNT(DISTINCT session_id) AS sess
80
80
  FROM context_chunks WHERE dedup_status != 'removed'`,
81
- )
82
- .get() as { kept: number; dropped: number; sess: number };
83
- repo.tokensKept = Number(tok.kept ?? 0);
84
- repo.tokensDropped = Number(tok.dropped ?? 0);
85
- repo.sessions = Number(tok.sess ?? 0);
86
- const mrow = sdb
87
- .prepare(
88
- `SELECT context_window, max_tokens, reasoning
81
+ )
82
+ .get() as { kept: number; dropped: number; sess: number };
83
+ repo.tokensKept = Number(tok.kept ?? 0);
84
+ repo.tokensDropped = Number(tok.dropped ?? 0);
85
+ repo.sessions = Number(tok.sess ?? 0);
86
+ const mrow = sdb
87
+ .prepare(
88
+ `SELECT context_window, max_tokens, reasoning
89
89
  FROM model_snapshots ORDER BY captured_at DESC LIMIT 1`,
90
- )
91
- .get() as { context_window: number; max_tokens: number; reasoning: number } | undefined;
92
- if (mrow) {
93
- repo.contextWindow = Number(mrow.context_window ?? 0) || null;
94
- repo.maxTokens = Number(mrow.max_tokens ?? 0) || null;
95
- repo.reasoning = Number(mrow.reasoning ?? 0) === 1;
96
- }
97
- // Provider prompt-cache stats (E.3)
98
- try {
99
- const pRow = sdb
100
- .prepare(
101
- `SELECT
90
+ )
91
+ .get() as
92
+ | {
93
+ context_window: number;
94
+ max_tokens: number;
95
+ reasoning: number;
96
+ }
97
+ | undefined;
98
+ if (mrow) {
99
+ repo.contextWindow = Number(mrow.context_window ?? 0) || null;
100
+ repo.maxTokens = Number(mrow.max_tokens ?? 0) || null;
101
+ repo.reasoning = Number(mrow.reasoning ?? 0) === 1;
102
+ }
103
+ // Provider prompt-cache stats (E.3)
104
+ try {
105
+ const pRow = sdb
106
+ .prepare(
107
+ `SELECT
102
108
  SUM(cache_read) AS totalRead,
103
109
  SUM(cache_write) AS totalWrite,
104
110
  SUM(cache_read + cache_write) * 1.0 / MAX(SUM(cache_read + cache_write + tokens_in), 1) AS hitPct
105
111
  FROM perf_samples
106
112
  WHERE cache_read > 0 OR cache_write > 0`,
107
- )
108
- .get() as { totalRead: number; totalWrite: number; hitPct: number } | undefined;
109
- if (pRow && pRow.totalRead > 0) {
110
- repo.providerCacheRead = Number(pRow.totalRead);
111
- repo.providerCacheWrite = Number(pRow.totalWrite);
112
- repo.providerCachePct = Number((pRow.hitPct * 100).toFixed(1));
113
- }
114
- } catch {
115
- /* best-effort — perf_samples may not exist */
116
- }
117
- } finally {
118
- sdb.close();
119
- }
120
- }
121
- } catch {
122
- /* best-effort — keep the defaults */
123
- }
124
- }
125
- // Defensive display hygiene (belt-and-suspenders — the real fix is that
126
- // tests now isolate via MEGACOMPACT_INDEX_DIR): drop transient test/temp
127
- // paths that should never have been real repos, and collapse duplicate
128
- // display names to the most-recently-seen row (rows are last_seen DESC, so
129
- // the first occurrence wins). Keeps the All-repos list readable.
130
- const isTransient = (p: string) =>
131
- /^\/tmp\//.test(p) || /^\/private\/tmp\//.test(p) || /^\/var\/folders\//.test(p) ||
132
- /\/mc-(ext|e2e|resume|recall)-/.test(p);
133
- const seenName = new Set<string>();
134
- const repos: IndexRepo[] = [];
135
- for (const r of mapped) {
136
- if (isTransient(r.repoRoot)) continue;
137
- if (seenName.has(r.displayName)) continue;
138
- seenName.add(r.displayName);
139
- repos.push(r);
140
- }
141
- const summary = {
142
- totalRepos: repos.length,
143
- totalCheckpoints: repos.reduce((a, r) => a + r.checkpointCount, 0),
144
- totalTokensSaved: repos.reduce((a, r) => a + r.tokensSaved, 0),
145
- totalCompressedOriginalBytes: repos.reduce((a, r) => a + r.compressedOriginalBytes, 0),
146
- };
147
- return { updatedAt: new Date().toISOString(), summary, repos };
148
- } catch {
149
- return null;
150
- } finally {
151
- try { db?.close(); } catch { /* ignore */ }
152
- }
113
+ )
114
+ .get() as
115
+ | { totalRead: number; totalWrite: number; hitPct: number }
116
+ | undefined;
117
+ if (pRow && pRow.totalRead > 0) {
118
+ repo.providerCacheRead = Number(pRow.totalRead);
119
+ repo.providerCacheWrite = Number(pRow.totalWrite);
120
+ repo.providerCachePct = Number((pRow.hitPct * 100).toFixed(1));
121
+ }
122
+ } catch {
123
+ /* best-effort — perf_samples may not exist */
124
+ }
125
+ } finally {
126
+ sdb.close();
127
+ }
128
+ }
129
+ } catch {
130
+ /* best-effort — keep the defaults */
131
+ }
132
+ }
133
+ // Defensive display hygiene (belt-and-suspenders the real fix is that
134
+ // tests now isolate via MEGACOMPACT_INDEX_DIR): drop transient test/temp
135
+ // paths that should never have been real repos, and collapse duplicate
136
+ // display names to the most-recently-seen row (rows are last_seen DESC, so
137
+ // the first occurrence wins). Keeps the All-repos list readable.
138
+ const isTransient = (p: string) =>
139
+ /^\/tmp\//.test(p) ||
140
+ /^\/private\/tmp\//.test(p) ||
141
+ /^\/var\/folders\//.test(p) ||
142
+ /\/mc-(ext|e2e|resume|recall)-/.test(p);
143
+ const seenName = new Set<string>();
144
+ const repos: IndexRepo[] = [];
145
+ for (const r of mapped) {
146
+ if (isTransient(r.repoRoot)) continue;
147
+ if (seenName.has(r.displayName)) continue;
148
+ seenName.add(r.displayName);
149
+ repos.push(r);
150
+ }
151
+ const summary = {
152
+ totalRepos: repos.length,
153
+ totalCheckpoints: repos.reduce((a, r) => a + r.checkpointCount, 0),
154
+ totalTokensSaved: repos.reduce((a, r) => a + r.tokensSaved, 0),
155
+ totalCompressedOriginalBytes: repos.reduce(
156
+ (a, r) => a + r.compressedOriginalBytes,
157
+ 0,
158
+ ),
159
+ };
160
+ return { updatedAt: new Date().toISOString(), summary, repos };
161
+ } catch {
162
+ return null;
163
+ } finally {
164
+ try {
165
+ db?.close();
166
+ } catch {
167
+ /* ignore */
168
+ }
169
+ }
153
170
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * GET /api/provider-cache — Lifetime provider prompt cache hit-rate aggregates
5
5
  * plus dollar savings estimate (priced from latest model snapshot).
6
+ * Accepts optional ?minutes=N, ?since=<epoch-ms>, ?until=<epoch-ms> (S53A).
6
7
  *
7
8
  * Guardrails: PREVENT-PI-004 (loopback-only), PREVENT-001 (null-safe JSON parse),
8
9
  * PREVENT-002 (parameterized SQL). Loopback-only localhost dashboard endpoint.
@@ -24,100 +25,175 @@ export function handleProviderCache(
24
25
  ctx: RouteContext,
25
26
  ): boolean {
26
27
  if (!req.url?.startsWith("/api/provider-cache")) return false;
27
-
28
- const { stateDir } = ctx;
29
-
30
- // /api/provider-cache — GET returns lifetime provider prompt cache aggregates
31
- // from perf_samples (cache_hit_pct rows with meta JSON) plus an optional
32
- // dollar savings estimate priced from the latest model snapshot. The dashboard
33
- // server is a detached child with no MegaRuntime ref, so it reads via a
34
- // require()'d sqlite helper (same pattern as /api/perf). Non-GET → 405.
35
- // PREVENT-PI-004: loopback.
36
- const pfReq = createRequire(import.meta.url);
37
- const { readProviderCacheLifetime, latestModelSnapshot } = pfReq(
38
- "../../src/store/sqlite.js",
39
- ) as typeof import("../../src/store/sqlite.js");
40
- if (req.method !== "GET") {
41
- // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
42
- res.writeHead(405, { "Content-Type": "application/json" });
43
- res.end(JSON.stringify({ error: "method_not_allowed" }));
44
- return true;
45
- }
46
28
  try {
47
- const lifetime = readProviderCacheLifetime(stateDir); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
29
+ const stateDir = ctx.stateDir;
30
+ // PREVENT-PI-004: loopback.
31
+ const pfReq = createRequire(import.meta.url);
32
+ const { readProviderCacheLifetime, readProviderCacheWindow, latestModelSnapshot } = pfReq(
33
+ "../../src/store/sqlite.js",
34
+ ) as typeof import("../../src/store/sqlite.js");
35
+ if (req.method !== "GET") {
36
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
37
+ res.writeHead(405, { "Content-Type": "application/json" });
38
+ res.end(JSON.stringify({ error: "method_not_allowed" }));
39
+ return true;
40
+ }
48
41
 
49
- // D3: compute dollar savings from the latest model snapshot. Null-safe:
50
- // when no snapshot or zero input rate, savings stays null (UI shows "—").
51
- let savings: {
52
- cacheReadSaved: number;
53
- cacheWriteCost: number;
54
- netSaved: number;
55
- model: string;
56
- inputRate: number;
57
- } | null = null;
58
- try {
59
- const snap = latestModelSnapshot(stateDir); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
60
- if (snap && snap.inputRate > 0) {
61
- const s = computeCacheSavings(
62
- lifetime.totalCacheRead,
63
- lifetime.totalCacheWrite,
64
- snap.inputRate,
65
- );
66
- savings = {
67
- cacheReadSaved: s.cacheReadSaved,
68
- cacheWriteCost: s.cacheWriteCost,
69
- netSaved: s.netSaved,
70
- model: snap.modelName ?? snap.modelId ?? "unknown",
71
- inputRate: snap.inputRate,
72
- };
42
+ // Parse query params: ?minutes=N | ?since=&until= (mutually exclusive).
43
+ let windowMinutes: number | null = null;
44
+ let sinceTs = 0;
45
+ let untilTs = 0;
46
+ if (req.url) {
47
+ const qIdx = req.url.indexOf("?");
48
+ if (qIdx !== -1) {
49
+ const params = new URLSearchParams(req.url.slice(qIdx));
50
+ const m = params.get("minutes");
51
+ if (m != null) {
52
+ const n = parseInt(m, 10);
53
+ if (Number.isFinite(n) && n > 0 && String(n) === m.trim()) {
54
+ windowMinutes = n;
55
+ }
56
+ }
57
+ // S53A: ?since=&until= epoch-ms timestamps.
58
+ const s = params.get("since");
59
+ const u = params.get("until");
60
+ if (s != null) {
61
+ const sn = Number(s);
62
+ if (Number.isFinite(sn) && sn >= 0) sinceTs = sn;
63
+ }
64
+ if (u != null) {
65
+ const un = Number(u);
66
+ if (Number.isFinite(un) && un > 0) untilTs = un;
67
+ }
73
68
  }
69
+ }
70
+
71
+ // S53A: read prefix_break samples in the same window as the aggregates.
72
+ // Import lazily to avoid a circular dependency in the test harness.
73
+ let prefixBreaks: Array<{
74
+ id: number;
75
+ ts: number;
76
+ cause: "recall" | "compaction" | "inject" | "other";
77
+ confidence: number;
78
+ prevHitPct: number;
79
+ currHitPct: number;
80
+ breakAt: number;
81
+ }> = [];
82
+ try {
83
+ // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
84
+ const { readPrefixBreaks } = pfReq(
85
+ "../../src/store/sqlite/perf-samples.js",
86
+ ) as typeof import("../../src/store/sqlite/perf-samples.js");
87
+ // When ?since=&until= is used, pass those directly; otherwise derive
88
+ // from windowMinutes or 0 (no bounds = lifetime).
89
+ const pSince = sinceTs > 0 ? sinceTs : (windowMinutes != null ? Date.now() - windowMinutes * 60_000 : 0);
90
+ const pUntil = untilTs > 0 ? untilTs : (windowMinutes != null ? Date.now() : 0);
91
+ const rows = readPrefixBreaks(stateDir, pSince, pUntil);
92
+ prefixBreaks = rows.map((r) => ({
93
+ id: r.id,
94
+ ts: r.ts,
95
+ cause: r.meta.cause,
96
+ confidence: r.meta.confidence,
97
+ prevHitPct: r.meta.prevHitPct,
98
+ currHitPct: r.meta.currHitPct,
99
+ breakAt: r.meta.breakAt,
100
+ }));
74
101
  } catch {
75
- /* pricing unavailable — savings stays null */
102
+ /* prefix-break read unavailable — response still returns empty array */
76
103
  }
77
104
 
78
- const body: {
79
- cache: {
80
- avgHitPct: number;
81
- turnCount: number;
82
- totalCacheRead: number;
83
- totalCacheWrite: number;
84
- totalInput: number;
85
- firstTurnAt: string | null;
86
- latestTurnAt: string | null;
87
- };
88
- savings: {
105
+ try {
106
+ const lifetime = windowMinutes != null
107
+ ? readProviderCacheWindow(stateDir, windowMinutes)
108
+ : readProviderCacheLifetime(stateDir); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
109
+
110
+ // D3: compute dollar savings from the latest model snapshot. Null-safe:
111
+ // when no snapshot or zero input rate, savings stays null (UI shows "—").
112
+ let savings: {
89
113
  cacheReadSaved: number;
90
114
  cacheWriteCost: number;
91
115
  netSaved: number;
92
116
  model: string;
93
117
  inputRate: number;
94
- } | null;
95
- updatedAt: string;
96
- } = {
97
- cache: {
98
- avgHitPct: lifetime.avgHitPct,
99
- turnCount: lifetime.sampleCount,
100
- totalCacheRead: lifetime.totalCacheRead,
101
- totalCacheWrite: lifetime.totalCacheWrite,
102
- totalInput: lifetime.totalInput,
103
- firstTurnAt: lifetime.firstSampleAt,
104
- latestTurnAt: lifetime.latestSampleAt,
105
- },
106
- savings,
107
- updatedAt: new Date().toISOString(),
108
- };
109
- // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
110
- res.writeHead(200, { "Content-Type": "application/json" });
111
- res.end(JSON.stringify(body));
112
- } catch (e) {
113
- // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
114
- res.writeHead(500, { "Content-Type": "application/json" });
115
- res.end(
116
- JSON.stringify({
117
- error: "provider_cache_unavailable",
118
- detail: String(e),
119
- }),
120
- );
118
+ } | null = null;
119
+ try {
120
+ const snap = latestModelSnapshot(stateDir); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
121
+ if (snap && snap.inputRate > 0) {
122
+ const s = computeCacheSavings(
123
+ lifetime.totalCacheRead,
124
+ lifetime.totalCacheWrite,
125
+ snap.inputRate,
126
+ );
127
+ savings = {
128
+ cacheReadSaved: s.cacheReadSaved,
129
+ cacheWriteCost: s.cacheWriteCost,
130
+ netSaved: s.netSaved,
131
+ model: snap.modelName ?? snap.modelId ?? "unknown",
132
+ inputRate: snap.inputRate,
133
+ };
134
+ }
135
+ } catch {
136
+ /* pricing unavailable — savings stays null */
137
+ }
138
+
139
+ const body: {
140
+ cache: {
141
+ avgHitPct: number;
142
+ turnCount: number;
143
+ totalCacheRead: number;
144
+ totalCacheWrite: number;
145
+ totalInput: number;
146
+ firstTurnAt: string | null;
147
+ latestTurnAt: string | null;
148
+ byModel: Array<{
149
+ model: string;
150
+ hitPct: number;
151
+ totalCacheRead: number;
152
+ totalCacheWrite: number;
153
+ sampleCount: number;
154
+ }>;
155
+ };
156
+ savings: {
157
+ cacheReadSaved: number;
158
+ cacheWriteCost: number;
159
+ netSaved: number;
160
+ model: string;
161
+ inputRate: number;
162
+ } | null;
163
+ updatedAt: string;
164
+ windowMinutes?: number | null;
165
+ prefixBreaks: typeof prefixBreaks;
166
+ } = {
167
+ cache: {
168
+ avgHitPct: lifetime.avgHitPct,
169
+ turnCount: lifetime.sampleCount,
170
+ totalCacheRead: lifetime.totalCacheRead,
171
+ totalCacheWrite: lifetime.totalCacheWrite,
172
+ totalInput: lifetime.totalInput,
173
+ firstTurnAt: lifetime.firstSampleAt,
174
+ latestTurnAt: lifetime.latestSampleAt,
175
+ byModel: lifetime.byModel,
176
+ },
177
+ savings,
178
+ updatedAt: new Date().toISOString(),
179
+ windowMinutes,
180
+ prefixBreaks,
181
+ };
182
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
183
+ res.writeHead(200, { "Content-Type": "application/json" });
184
+ res.end(JSON.stringify(body));
185
+ } catch (e) {
186
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
187
+ res.writeHead(500, { "Content-Type": "application/json" });
188
+ res.end(
189
+ JSON.stringify({
190
+ error: "provider_cache_unavailable",
191
+ detail: String(e),
192
+ }),
193
+ );
194
+ }
195
+ return true;
196
+ } catch {
197
+ return false;
121
198
  }
122
- return true;
123
- }
199
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * dashboard-server/routes-memory.ts — Memory effectiveness stats route handler (S53B).
3
+ *
4
+ * GET /api/memory-status — Returns memoryStats() aggregated data:
5
+ * totalMemories, memoriesInLast30Days, topStableMemories, avgRecallScore.
6
+ *
7
+ * Guardrails: PREVENT-PI-004 (loopback-only), PREVENT-001 (null-safe JSON),
8
+ * PREVENT-002 (parameterized SQL — memoryStats uses openStore internally).
9
+ * Loopback-only localhost dashboard endpoint.
10
+ */
11
+
12
+ import { createRequire } from "node:module";
13
+ import type { IncomingMessage, ServerResponse } from "node:http";
14
+ import type { RouteContext } from "./routes-core.js";
15
+ import type { MemoryStatusResponse } from "./api-contracts/memory.js";
16
+
17
+ // PREVENT-PI-004: memoryStats is a pure local module (no network, no side-effects at import time).
18
+ // guardrails-allow PREVENT-PI-004: local TypeScript module import (loopback-only).
19
+ const { memoryStats } = createRequire(import.meta.url)(
20
+ "../../src/memoryStats.js",
21
+ ) as typeof import("../../src/memoryStats.js");
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // handleMemoryStatus — "/api/memory-status"
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export function handleMemoryStatus(
28
+ req: IncomingMessage,
29
+ res: ServerResponse,
30
+ ctx: RouteContext,
31
+ ): boolean {
32
+ if (!req.url?.startsWith("/api/memory-status")) return false;
33
+ if (req.method !== "GET") {
34
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
35
+ res.writeHead(405, { "Content-Type": "application/json" });
36
+ res.end(JSON.stringify({ error: "method_not_allowed" }));
37
+ return true;
38
+ }
39
+ // PREVENT-PI-004: loopback-only — memoryStats reads from local SQLite store.
40
+ memoryStats(ctx.stateDir)
41
+ .then((stats) => {
42
+ const body: MemoryStatusResponse = {
43
+ totalMemories: stats.totalMemories,
44
+ memoriesInLast30Days: stats.memoriesInLast30Days,
45
+ topStableMemories: stats.topStableMemories.map((m) => ({
46
+ id: m.id,
47
+ text: m.text,
48
+ recallCount: m.recallCount,
49
+ lastRecalledAt: m.lastRecalledAt,
50
+ })),
51
+ avgRecallScore: stats.avgRecallScore,
52
+ };
53
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
54
+ res.writeHead(200, { "Content-Type": "application/json" });
55
+ res.end(JSON.stringify(body));
56
+ })
57
+ .catch(() => {
58
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
59
+ res.writeHead(500, { "Content-Type": "application/json" });
60
+ res.end(JSON.stringify({ error: "memory_stats_unavailable" }));
61
+ });
62
+ return true;
63
+ }