pi-mega-compact 0.11.4 → 0.11.6

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 (381) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/cache-stripes.js +8 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +32 -0
  3. package/dist/extensions/dashboard-server/api-contracts/memory-map.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/memory.js +8 -0
  5. package/dist/extensions/dashboard-server/api-contracts/provider-cache.js +1 -0
  6. package/dist/extensions/dashboard-server/api-contracts/setup.js +10 -0
  7. package/dist/extensions/dashboard-server/index-reader.js +6 -2
  8. package/dist/extensions/dashboard-server/routes-cache.js +135 -47
  9. package/dist/extensions/dashboard-server/routes-game.js +3 -0
  10. package/dist/extensions/dashboard-server/routes-memory-map.js +50 -0
  11. package/dist/extensions/dashboard-server/routes-memory.js +51 -0
  12. package/dist/extensions/dashboard-server/routes-repo.js +2 -2
  13. package/dist/extensions/dashboard-server/routes-setup.js +267 -0
  14. package/dist/extensions/dashboard-server/routes.js +4 -0
  15. package/dist/extensions/dashboard-server/server.js +13 -1
  16. package/dist/extensions/mega-commands.js +189 -1
  17. package/dist/extensions/mega-compact.js +5 -0
  18. package/dist/extensions/mega-config.js +7 -0
  19. package/dist/extensions/mega-events/agent-handlers.js +19 -4
  20. package/dist/extensions/mega-events/compact-handlers.js +1 -0
  21. package/dist/extensions/mega-events/context-handler.js +45 -92
  22. package/dist/extensions/mega-events/context-hwm.js +51 -0
  23. package/dist/extensions/mega-events/error-classifier.js +16 -5
  24. package/dist/extensions/mega-events/mirror-append.js +107 -0
  25. package/dist/extensions/mega-events/outage-advisor.js +7 -1
  26. package/dist/extensions/mega-events/perf-handler.js +134 -2
  27. package/dist/extensions/mega-events/recall-tail.js +59 -0
  28. package/dist/extensions/mega-events/separated-prompt.js +248 -0
  29. package/dist/extensions/mega-events/session-handlers.js +15 -8
  30. package/dist/extensions/mega-runtime/env-loader.js +56 -0
  31. package/dist/extensions/mega-runtime/reset-runtime.js +6 -0
  32. package/dist/extensions/mega-runtime/runtime-snapshot.js +1 -0
  33. package/dist/extensions/mega-runtime/runtime.js +7 -0
  34. package/dist/extensions/mega-runtime/snapshot.js +1 -0
  35. package/dist/extensions/mega-runtime/widget.js +5 -0
  36. package/dist/src/cache-stripe-impl.js +288 -0
  37. package/dist/src/cache-stripe.js +11 -0
  38. package/dist/src/config/stopwords.js +24 -0
  39. package/dist/src/config.js +17 -0
  40. package/dist/src/embedder.js +41 -2
  41. package/dist/src/memoryGraph.js +299 -0
  42. package/dist/src/memoryStats.js +75 -0
  43. package/dist/src/monitoring.js +13 -0
  44. package/dist/src/prefix-break.js +43 -0
  45. package/dist/src/pricing.js +1 -1
  46. package/dist/src/queryExpansion.js +163 -0
  47. package/dist/src/queryReformulation/cache.js +123 -0
  48. package/dist/src/queryReformulation/rrf.js +29 -0
  49. package/dist/src/queryReformulation/tfidf.js +61 -0
  50. package/dist/src/queryReformulation/vagueness.js +33 -0
  51. package/dist/src/queryReformulation.js +225 -0
  52. package/dist/src/recallMetrics.js +258 -0
  53. package/dist/src/store/sqlite/cache-stripes.js +133 -0
  54. package/dist/src/store/sqlite/fts5-search.js +68 -0
  55. package/dist/src/store/sqlite/perf-samples.js +145 -16
  56. package/dist/src/store/sqlite/schema.js +60 -1
  57. package/dist/src/store/sqlite.js +1 -0
  58. package/dist/src/store/turns/memory-store.js +8 -0
  59. package/dist/src/store/turns/sqlite-store.js +14 -3
  60. package/dist/src/store/turns/types.js +18 -1
  61. package/dist/src/store/vectorIndex.js +55 -10
  62. package/dist/src/tiered-router/types.js +18 -0
  63. package/dist/src/tieredRouter.js +421 -0
  64. package/dist/src/vectorStore.js +8 -6
  65. package/extensions/dashboard-client/dist/assets/{AchievementTiles-vEo2MtE2.js → AchievementTiles-DR9cLGDL.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{AchievementTiles-vEo2MtE2.js.map → AchievementTiles-DR9cLGDL.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/AchievementsTab-svNUgJ1e.js +2 -0
  68. package/extensions/dashboard-client/dist/assets/{AchievementsTab-DjxfYrDc.js.map → AchievementsTab-svNUgJ1e.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js +2 -0
  70. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js.map +1 -0
  71. package/extensions/dashboard-client/dist/assets/{ConfigTab-Cm6ymbmx.js → ConfigTab-CmmWheuW.js} +2 -2
  72. package/extensions/dashboard-client/dist/assets/{ConfigTab-Cm6ymbmx.js.map → ConfigTab-CmmWheuW.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{EventsTab-DXWl_YzI.js → EventsTab-CFE7jLsa.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{EventsTab-DXWl_YzI.js.map → EventsTab-CFE7jLsa.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/GameTab-CJAX_th_.js +2 -0
  76. package/extensions/dashboard-client/dist/assets/{GameTab-BLDudcO5.js.map → GameTab-CJAX_th_.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DLkKCztn.js → MaintenanceTab-P4SUsvMf.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DLkKCztn.js.map → MaintenanceTab-P4SUsvMf.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +2 -0
  80. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +1 -0
  81. package/extensions/dashboard-client/dist/assets/{MetricsTab-Cq4u-rZF.js → MetricsTab-CHQscixy.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{MetricsTab-Cq4u-rZF.js.map → MetricsTab-CHQscixy.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/OverviewTab-DBs4DJdd.js +2 -0
  84. package/extensions/dashboard-client/dist/assets/{OverviewTab-D4kGvsiQ.js.map → OverviewTab-DBs4DJdd.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/ReposTab-DeFz76L5.js +2 -0
  86. package/extensions/dashboard-client/dist/assets/ReposTab-DeFz76L5.js.map +1 -0
  87. package/extensions/dashboard-client/dist/assets/{SessionsTab-DZC2-GCn.js → SessionsTab-BWNsj1OB.js} +2 -2
  88. package/extensions/dashboard-client/dist/assets/{SessionsTab-DZC2-GCn.js.map → SessionsTab-BWNsj1OB.js.map} +1 -1
  89. package/extensions/dashboard-client/dist/assets/SetupTab-uS6A0xmw.js +2 -0
  90. package/extensions/dashboard-client/dist/assets/SetupTab-uS6A0xmw.js.map +1 -0
  91. package/extensions/dashboard-client/dist/assets/TimeSavedCard-CiqCOQuL.js +2 -0
  92. package/extensions/dashboard-client/dist/assets/TimeSavedCard-CiqCOQuL.js.map +1 -0
  93. package/extensions/dashboard-client/dist/assets/{TopicsTab-01G71hv1.js → TopicsTab-BWWQxltv.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{TopicsTab-01G71hv1.js.map → TopicsTab-BWWQxltv.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/assets/{TurnsTab-DLRg0LZT.js → TurnsTab-CVz4f4-o.js} +2 -2
  96. package/extensions/dashboard-client/dist/assets/{TurnsTab-DLRg0LZT.js.map → TurnsTab-CVz4f4-o.js.map} +1 -1
  97. package/extensions/dashboard-client/dist/assets/format-COjDIVGj.js +2 -0
  98. package/extensions/dashboard-client/dist/assets/format-COjDIVGj.js.map +1 -0
  99. package/extensions/dashboard-client/dist/assets/index-CUNn22iV.css +1 -0
  100. package/extensions/dashboard-client/dist/assets/index-DrktNOoT.js +42 -0
  101. package/extensions/dashboard-client/dist/assets/index-DrktNOoT.js.map +1 -0
  102. package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js +2 -0
  103. package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js.map +1 -0
  104. package/extensions/dashboard-client/dist/assets/{useSSE-BBYt5UPB.js → useSSE-Bf5LOLSi.js} +2 -2
  105. package/extensions/dashboard-client/dist/assets/{useSSE-BBYt5UPB.js.map → useSSE-Bf5LOLSi.js.map} +1 -1
  106. package/extensions/dashboard-client/dist/index.html +2 -2
  107. package/extensions/dashboard-client/src/App.tsx +28 -8
  108. package/extensions/dashboard-client/src/api/client.ts +29 -0
  109. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +21 -12
  110. package/extensions/dashboard-client/src/components/CacheHitRateTrendCard.tsx +100 -0
  111. package/extensions/dashboard-client/src/components/ProviderCacheCard.tsx +56 -0
  112. package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +38 -57
  113. package/extensions/dashboard-client/src/components/StripeDistributionCard.tsx +110 -0
  114. package/extensions/dashboard-client/src/components/TabBar.tsx +49 -4
  115. package/extensions/dashboard-client/src/main.tsx +1 -0
  116. package/extensions/dashboard-client/src/styles/base.css +30 -0
  117. package/extensions/dashboard-client/src/styles/cache.css +167 -0
  118. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +75 -12
  119. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +440 -0
  120. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +418 -0
  121. package/extensions/dashboard-client/tsconfig.json +3 -2
  122. package/extensions/dashboard-client/vite.config.ts +2 -0
  123. package/extensions/dashboard-server/api-contracts/cache-stripes.ts +49 -0
  124. package/extensions/dashboard-server/api-contracts/endpoints.ts +66 -3
  125. package/extensions/dashboard-server/api-contracts/index.ts +22 -0
  126. package/extensions/dashboard-server/api-contracts/memory-map.ts +50 -0
  127. package/extensions/dashboard-server/api-contracts/memory.ts +27 -0
  128. package/extensions/dashboard-server/api-contracts/multi-repo.ts +231 -231
  129. package/extensions/dashboard-server/api-contracts/provider-cache.ts +37 -0
  130. package/extensions/dashboard-server/api-contracts/setup.ts +80 -0
  131. package/extensions/dashboard-server/index-reader.ts +138 -121
  132. package/extensions/dashboard-server/routes-cache.ts +186 -75
  133. package/extensions/dashboard-server/routes-game.ts +3 -0
  134. package/extensions/dashboard-server/routes-memory-map.ts +105 -0
  135. package/extensions/dashboard-server/routes-memory.ts +63 -0
  136. package/extensions/dashboard-server/routes-repo.ts +36 -36
  137. package/extensions/dashboard-server/routes-setup.ts +293 -0
  138. package/extensions/dashboard-server/routes.ts +4 -0
  139. package/extensions/dashboard-server/server.ts +12 -0
  140. package/extensions/mega-commands.ts +211 -1
  141. package/extensions/mega-compact.ts +5 -0
  142. package/extensions/mega-config.ts +27 -0
  143. package/extensions/mega-events/agent-handlers.ts +20 -4
  144. package/extensions/mega-events/compact-handlers.ts +1 -0
  145. package/extensions/mega-events/context-handler.ts +53 -110
  146. package/extensions/mega-events/context-hwm.ts +85 -0
  147. package/extensions/mega-events/error-classifier.ts +15 -4
  148. package/extensions/mega-events/mirror-append.ts +155 -0
  149. package/extensions/mega-events/outage-advisor.ts +11 -5
  150. package/extensions/mega-events/perf-handler.ts +190 -6
  151. package/extensions/mega-events/recall-tail.ts +88 -0
  152. package/extensions/mega-events/separated-prompt.ts +329 -0
  153. package/extensions/mega-events/session-handlers.ts +14 -7
  154. package/extensions/mega-runtime/env-loader.ts +55 -0
  155. package/extensions/mega-runtime/helpers.ts +14 -0
  156. package/extensions/mega-runtime/reset-runtime.ts +6 -0
  157. package/extensions/mega-runtime/runtime-snapshot.ts +1 -0
  158. package/extensions/mega-runtime/runtime.ts +7 -0
  159. package/extensions/mega-runtime/snapshot.ts +6 -0
  160. package/extensions/mega-runtime/widget-types.ts +3 -0
  161. package/extensions/mega-runtime/widget.ts +5 -0
  162. package/package.json +5 -2
  163. package/src/cache-stripe-impl.ts +385 -0
  164. package/src/cache-stripe.ts +18 -0
  165. package/src/config/stopwords.ts +25 -0
  166. package/src/config.ts +26 -0
  167. package/src/embedder.ts +42 -2
  168. package/src/memoryGraph.ts +438 -0
  169. package/src/memoryStats.ts +107 -0
  170. package/src/monitoring.ts +35 -0
  171. package/src/prefix-break.ts +83 -0
  172. package/src/pricing.ts +2 -2
  173. package/src/queryExpansion.ts +206 -0
  174. package/src/queryReformulation/cache.ts +169 -0
  175. package/src/queryReformulation/rrf.ts +51 -0
  176. package/src/queryReformulation/tfidf.ts +79 -0
  177. package/src/queryReformulation/vagueness.ts +38 -0
  178. package/src/queryReformulation.ts +355 -0
  179. package/src/recallMetrics.ts +388 -0
  180. package/src/store/sqlite/cache-stripes.ts +196 -0
  181. package/src/store/sqlite/fts5-search.ts +90 -0
  182. package/src/store/sqlite/perf-samples.ts +218 -23
  183. package/src/store/sqlite/schema.ts +60 -1
  184. package/src/store/sqlite.ts +1 -0
  185. package/src/store/turns/memory-store.ts +10 -1
  186. package/src/store/turns/sqlite-store.ts +27 -15
  187. package/src/store/turns/types.ts +21 -0
  188. package/src/store/vectorIndex.ts +71 -11
  189. package/src/tiered-router/types.ts +64 -0
  190. package/src/tieredRouter.ts +489 -0
  191. package/src/vectorStore.ts +9 -5
  192. package/dist/extensions/conflict-scan.test.js +0 -115
  193. package/dist/extensions/dashboard-server/api-contracts.test.js +0 -884
  194. package/dist/extensions/dashboard-server/html.test.js +0 -52
  195. package/dist/extensions/dashboard-server/perf-server.test.js +0 -80
  196. package/dist/extensions/dashboard-server/routes-cache.test.js +0 -248
  197. package/dist/extensions/dashboard-server/server.test.js +0 -120
  198. package/dist/extensions/dashboard-server-s32.test.js +0 -181
  199. package/dist/extensions/dashboard-server.test.js +0 -339
  200. package/dist/extensions/mega-cache-replay.test.js +0 -400
  201. package/dist/extensions/mega-compact-s38.test.js +0 -1383
  202. package/dist/extensions/mega-compact.test.js +0 -1112
  203. package/dist/extensions/mega-events.test.js +0 -47
  204. package/dist/extensions/mega-game-cmds.test.js +0 -163
  205. package/dist/extensions/mega-runtime/snapshot.test.js +0 -127
  206. package/dist/extensions/mega-runtime/state.test.js +0 -266
  207. package/dist/extensions/mega-runtime/widget.test.js +0 -343
  208. package/dist/extensions/mega-shutdown-widget.test.js +0 -121
  209. package/dist/extensions/mega-teamrun.test.js +0 -156
  210. package/dist/extensions/mega-trim.test.js +0 -58
  211. package/dist/extensions/mega-turn-store.test.js +0 -68
  212. package/dist/src/boundary.test.js +0 -170
  213. package/dist/src/compact.test.js +0 -78
  214. package/dist/src/config/themes.test.js +0 -94
  215. package/dist/src/dedup/dedup.test.js +0 -41
  216. package/dist/src/dedup/l1.test.js +0 -50
  217. package/dist/src/dedup/raptor/buildHistory.test.js +0 -292
  218. package/dist/src/dedup/raptor/multilevel-serve.test.js +0 -229
  219. package/dist/src/dedup/raptor/multilevel.test.js +0 -238
  220. package/dist/src/dedup/raptor/promote.test.js +0 -69
  221. package/dist/src/dedup/raptor/raptor.test.js +0 -248
  222. package/dist/src/dedup/raptor/retrieval.test.js +0 -95
  223. package/dist/src/dedup/raptor/serve-gate.test.js +0 -298
  224. package/dist/src/dedup/sprint12.test.js +0 -219
  225. package/dist/src/dedup-engine.test.js +0 -472
  226. package/dist/src/driftDetection.test.js +0 -87
  227. package/dist/src/e2e.test.js +0 -698
  228. package/dist/src/engine.test.js +0 -111
  229. package/dist/src/extractive.test.js +0 -147
  230. package/dist/src/fork.test.js +0 -81
  231. package/dist/src/game/scoring.test.js +0 -98
  232. package/dist/src/httpEmbedder.test.js +0 -277
  233. package/dist/src/importance.test.js +0 -372
  234. package/dist/src/intent.test.js +0 -78
  235. package/dist/src/log.test.js +0 -42
  236. package/dist/src/mechanical-fix.test.js +0 -65
  237. package/dist/src/memory.test.js +0 -70
  238. package/dist/src/memoryConsolidate.test.js +0 -38
  239. package/dist/src/memoryOps.test.js +0 -129
  240. package/dist/src/memoryRecall.test.js +0 -171
  241. package/dist/src/memoryRoundtrip.test.js +0 -137
  242. package/dist/src/metrics/turns.test.js +0 -152
  243. package/dist/src/mirror/mirror.test.js +0 -185
  244. package/dist/src/raptor-inject-summaries.test.js +0 -162
  245. package/dist/src/ratio.bench.test.js +0 -897
  246. package/dist/src/recall.integration.test.js +0 -77
  247. package/dist/src/recall.test.js +0 -379
  248. package/dist/src/sprint14.test.js +0 -219
  249. package/dist/src/sprint4x-rag-verification.test.js +0 -93
  250. package/dist/src/store/compression.test.js +0 -139
  251. package/dist/src/store/memoryIndex.test.js +0 -51
  252. package/dist/src/store/migrate.test.js +0 -139
  253. package/dist/src/store/pgOpenGuard.test.js +0 -74
  254. package/dist/src/store/phase01.test.js +0 -81
  255. package/dist/src/store/phase04.test.js +0 -54
  256. package/dist/src/store/sprint10.test.js +0 -186
  257. package/dist/src/store/sqlite/game-achievements.test.js +0 -67
  258. package/dist/src/store/sqlite/game-scores.test.js +0 -106
  259. package/dist/src/store/sqlite/game-state.test.js +0 -76
  260. package/dist/src/store/sqlite/global-index.test.js +0 -136
  261. package/dist/src/store/sqlite/mechanical-fix.test.js +0 -146
  262. package/dist/src/store/sqlite/perf-samples.test.js +0 -146
  263. package/dist/src/store/sqlite/raptor.test.js +0 -106
  264. package/dist/src/store/sqlite/turns.test.js +0 -183
  265. package/dist/src/store/sqlite.cachehit.test.js +0 -55
  266. package/dist/src/store/sqlite.dbmaint.test.js +0 -158
  267. package/dist/src/store/sqlite.dbmirror.test.js +0 -184
  268. package/dist/src/store/turns/contract-compliance.test.js +0 -428
  269. package/dist/src/store/turns/memory-store.test.js +0 -9
  270. package/dist/src/store/turns/migrations.test.js +0 -138
  271. package/dist/src/store/turns/sqlite-store.test.js +0 -144
  272. package/dist/src/store/vectorIndex.test.js +0 -123
  273. package/dist/src/store.test.js +0 -161
  274. package/dist/src/supersede.test.js +0 -36
  275. package/dist/src/topics/cluster.test.js +0 -150
  276. package/dist/src/topics/store.test.js +0 -117
  277. package/dist/src/types.test.js +0 -9
  278. package/dist/src/vector-search-cache.test.js +0 -157
  279. package/dist/src/vectorStore.test.js +0 -479
  280. package/dist/src/wiki.test.js +0 -171
  281. package/extensions/conflict-scan.test.ts +0 -129
  282. package/extensions/dashboard-client/dist/assets/AchievementsTab-DjxfYrDc.js +0 -2
  283. package/extensions/dashboard-client/dist/assets/CacheTab-Dnpv94wq.js +0 -2
  284. package/extensions/dashboard-client/dist/assets/CacheTab-Dnpv94wq.js.map +0 -1
  285. package/extensions/dashboard-client/dist/assets/GameTab-BLDudcO5.js +0 -2
  286. package/extensions/dashboard-client/dist/assets/OverviewTab-D4kGvsiQ.js +0 -2
  287. package/extensions/dashboard-client/dist/assets/ReposTab-C0Wnrf2u.js +0 -2
  288. package/extensions/dashboard-client/dist/assets/ReposTab-C0Wnrf2u.js.map +0 -1
  289. package/extensions/dashboard-client/dist/assets/TimeSavedCard-UqljSyP-.js +0 -2
  290. package/extensions/dashboard-client/dist/assets/TimeSavedCard-UqljSyP-.js.map +0 -1
  291. package/extensions/dashboard-client/dist/assets/index-BEdKlpAB.css +0 -1
  292. package/extensions/dashboard-client/dist/assets/index-BX0bcgLu.js +0 -42
  293. package/extensions/dashboard-client/dist/assets/index-BX0bcgLu.js.map +0 -1
  294. package/extensions/dashboard-server/api-contracts.test.ts +0 -1065
  295. package/extensions/dashboard-server/html.test.ts +0 -62
  296. package/extensions/dashboard-server/perf-server.test.ts +0 -101
  297. package/extensions/dashboard-server/routes-cache.test.ts +0 -302
  298. package/extensions/dashboard-server/server.test.ts +0 -131
  299. package/extensions/dashboard-server-s32.test.ts +0 -195
  300. package/extensions/dashboard-server.test.ts +0 -382
  301. package/extensions/mega-cache-replay.test.ts +0 -526
  302. package/extensions/mega-compact-s38.test.ts +0 -1433
  303. package/extensions/mega-compact.test.ts +0 -1509
  304. package/extensions/mega-events.test.ts +0 -55
  305. package/extensions/mega-game-cmds.test.ts +0 -197
  306. package/extensions/mega-runtime/snapshot.test.ts +0 -139
  307. package/extensions/mega-runtime/state.test.ts +0 -262
  308. package/extensions/mega-runtime/widget.test.ts +0 -468
  309. package/extensions/mega-shutdown-widget.test.ts +0 -141
  310. package/extensions/mega-teamrun.test.ts +0 -176
  311. package/extensions/mega-trim.test.ts +0 -64
  312. package/extensions/mega-turn-store.test.ts +0 -108
  313. package/src/boundary.test.ts +0 -187
  314. package/src/compact.test.ts +0 -99
  315. package/src/config/themes.test.ts +0 -116
  316. package/src/dedup/dedup.test.ts +0 -46
  317. package/src/dedup/l1.test.ts +0 -57
  318. package/src/dedup/raptor/buildHistory.test.ts +0 -353
  319. package/src/dedup/raptor/multilevel-serve.test.ts +0 -273
  320. package/src/dedup/raptor/multilevel.test.ts +0 -325
  321. package/src/dedup/raptor/promote.test.ts +0 -82
  322. package/src/dedup/raptor/raptor.test.ts +0 -297
  323. package/src/dedup/raptor/retrieval.test.ts +0 -118
  324. package/src/dedup/raptor/serve-gate.test.ts +0 -348
  325. package/src/dedup/sprint12.test.ts +0 -242
  326. package/src/dedup-engine.test.ts +0 -670
  327. package/src/driftDetection.test.ts +0 -100
  328. package/src/e2e.test.ts +0 -843
  329. package/src/engine.test.ts +0 -123
  330. package/src/extractive.test.ts +0 -174
  331. package/src/fork.test.ts +0 -94
  332. package/src/game/scoring.test.ts +0 -103
  333. package/src/httpEmbedder.test.ts +0 -286
  334. package/src/importance.test.ts +0 -538
  335. package/src/intent.test.ts +0 -87
  336. package/src/log.test.ts +0 -47
  337. package/src/mechanical-fix.test.ts +0 -70
  338. package/src/memory.test.ts +0 -92
  339. package/src/memoryConsolidate.test.ts +0 -47
  340. package/src/memoryOps.test.ts +0 -148
  341. package/src/memoryRecall.test.ts +0 -194
  342. package/src/memoryRoundtrip.test.ts +0 -155
  343. package/src/metrics/turns.test.ts +0 -165
  344. package/src/mirror/mirror.test.ts +0 -240
  345. package/src/raptor-inject-summaries.test.ts +0 -228
  346. package/src/ratio.bench.test.ts +0 -1316
  347. package/src/recall.integration.test.ts +0 -96
  348. package/src/recall.test.ts +0 -467
  349. package/src/sprint14.test.ts +0 -245
  350. package/src/sprint4x-rag-verification.test.ts +0 -119
  351. package/src/store/compression.test.ts +0 -168
  352. package/src/store/memoryIndex.test.ts +0 -61
  353. package/src/store/migrate.test.ts +0 -158
  354. package/src/store/pgOpenGuard.test.ts +0 -89
  355. package/src/store/phase01.test.ts +0 -88
  356. package/src/store/phase04.test.ts +0 -58
  357. package/src/store/sprint10.test.ts +0 -214
  358. package/src/store/sqlite/game-achievements.test.ts +0 -80
  359. package/src/store/sqlite/game-scores.test.ts +0 -132
  360. package/src/store/sqlite/game-state.test.ts +0 -89
  361. package/src/store/sqlite/global-index.test.ts +0 -222
  362. package/src/store/sqlite/mechanical-fix.test.ts +0 -162
  363. package/src/store/sqlite/perf-samples.test.ts +0 -169
  364. package/src/store/sqlite/raptor.test.ts +0 -139
  365. package/src/store/sqlite/turns.test.ts +0 -218
  366. package/src/store/sqlite.cachehit.test.ts +0 -68
  367. package/src/store/sqlite.dbmaint.test.ts +0 -193
  368. package/src/store/sqlite.dbmirror.test.ts +0 -229
  369. package/src/store/turns/contract-compliance.test.ts +0 -562
  370. package/src/store/turns/memory-store.test.ts +0 -15
  371. package/src/store/turns/migrations.test.ts +0 -161
  372. package/src/store/turns/sqlite-store.test.ts +0 -161
  373. package/src/store/vectorIndex.test.ts +0 -140
  374. package/src/store.test.ts +0 -191
  375. package/src/supersede.test.ts +0 -42
  376. package/src/topics/cluster.test.ts +0 -176
  377. package/src/topics/store.test.ts +0 -132
  378. package/src/types.test.ts +0 -10
  379. package/src/vector-search-cache.test.ts +0 -190
  380. package/src/vectorStore.test.ts +0 -533
  381. package/src/wiki.test.ts +0 -217
@@ -1,1509 +0,0 @@
1
- /**
2
- * mega-compact.extension.test.ts — end-to-end drive of the REAL extension
3
- * entry (extensions/mega-compact.ts) through a faithful mock pi.
4
- *
5
- * This is the closest we get to "a live pi session" without a model: it
6
- * loads the compiled extension, captures its event/command handlers, and
7
- * fires them with mock ctx objects — proving the three compact layers
8
- * (auto-trigger -> compactSession) AND the three recall entries all
9
- * route through the real code, not just the unit-tested src/ modules.
10
- *
11
- * Uses a per-test isolated state dir (process.env.MEGACOMPACT_STATE_DIR)
12
- * so concurrent node --test runs do not collide on disk.
13
- */
14
-
15
- import { test } from "node:test";
16
- import assert from "node:assert/strict";
17
- import { mkdtempSync, rmSync } from "node:fs";
18
- import { tmpdir } from "node:os";
19
- import { join } from "node:path";
20
- import { createRequire } from "node:module";
21
- import { closeVectorIndex } from "../src/store/vectorIndex.js";
22
- import type { AgentMessage } from "@earendil-works/pi-agent-core";
23
-
24
- const require = createRequire(import.meta.url);
25
- const baseTmp = mkdtempSync(join(tmpdir(), "mc-ext-"));
26
- // Isolate the machine-wide repo index so test runs (which call bindRepo ->
27
- // upsertRepoRegistry) never pollute the developer's real ~/.mega-compact-index.
28
- process.env.MEGACOMPACT_INDEX_DIR = join(baseTmp, "index");
29
- let counter = 0;
30
-
31
- /** Build a mock pi + ctx and load the extension into them. */
32
- function harness(opts: { keepTier?: boolean; keepThreshold?: boolean } = {}) {
33
- const stateDir = join(baseTmp, `run-${counter++}`);
34
- process.env.MEGACOMPACT_STATE_DIR = stateDir;
35
- process.env.MEGACOMPACT_DEBUG = "true";
36
- // Low threshold so the auto-trigger gate trips on our small mock context.
37
- // Tier tests opt out (keepTier/keepThreshold) so they can drive the real
38
- // tier resolution instead of the forced 50-token threshold.
39
- if (!opts.keepThreshold) process.env.MEGACOMPACT_THRESHOLD_TOKENS = "50";
40
- if (!opts.keepTier) delete process.env.MEGACOMPACT_TIER;
41
- process.env.MEGACOMPACT_FAST_GATE_PCT = "1";
42
-
43
- const handlers: Record<string, Function[]> = {};
44
- const commands: Record<
45
- string,
46
- { handler: (a: string, c: any) => Promise<void> }
47
- > = {};
48
- const appended: any[] = [];
49
- let statusKey: string | undefined;
50
- let statusText: string | undefined;
51
- const notifies: string[] = [];
52
- const compactCalls: any[] = [];
53
- const sendUserMessages: string[] = [];
54
-
55
- // Minimal AgentMessage factory for the session we project into the extension.
56
- function msg(role: string, text: string, toolName?: string): AgentMessage {
57
- if (role === "assistant" && toolName) {
58
- return {
59
- role: "assistant",
60
- content: [
61
- { type: "toolCall", name: toolName, id: "c1", arguments: {} },
62
- ],
63
- api: "anthropic-messages",
64
- provider: "anthropic",
65
- model: "m",
66
- usage: {
67
- inputTokens: 1,
68
- outputTokens: 1,
69
- cacheReadTokens: 0,
70
- cacheWriteTokens: 0,
71
- },
72
- stopReason: "tool_use",
73
- timestamp: 0,
74
- } as unknown as AgentMessage;
75
- }
76
- if (role === "toolResult" && toolName) {
77
- return {
78
- role: "toolResult",
79
- toolCallId: "c1",
80
- toolName,
81
- content: [{ type: "text", text }],
82
- isError: false,
83
- timestamp: 0,
84
- } as unknown as AgentMessage;
85
- }
86
- return {
87
- role: "user",
88
- content: text,
89
- timestamp: 0,
90
- } as unknown as AgentMessage;
91
- }
92
-
93
- const session: AgentMessage[] = [
94
- msg("user", "read src/vec.ts and understand the index"),
95
- msg("assistant", "ok", "Read"),
96
- msg("user", "edit src/vec.ts to add a cosine helper"),
97
- msg("assistant", "ok", "Edit"),
98
- msg("user", "now fix the dedupe bug in store.ts"),
99
- msg("assistant", "ok", "Edit"),
100
- msg("user", "actually we should add recall sorting too"),
101
- msg("assistant", "ok", "Edit"),
102
- ];
103
-
104
- // Mirror the REAL SessionManager: getEntries() returns SessionEntry objects,
105
- // which the extension projects to messages via the SDK's
106
- // sessionEntryToContextMessages(entry). The harness must use the same shape
107
- // (type:"message" with a .message) or recentUserQuery() silently queries "".
108
- const toEntry = (m: AgentMessage, i: number): any => ({
109
- type: "message",
110
- id: `e${i}`,
111
- parentId: null,
112
- timestamp: String(i),
113
- message: m,
114
- });
115
- const sessionManager = {
116
- getSessionId: () => "sess_ext_001",
117
- getEntries: () => session.map(toEntry),
118
- // Faithful mock: getBranch() returns the current branch's entries, which
119
- // piCompactWouldNoop() reads to predict whether ctx.compact() would no-op.
120
- getBranch: () => session.map(toEntry),
121
- };
122
-
123
- function makeCtx(over: Partial<any> = {}) {
124
- return {
125
- ui: {
126
- setStatus: (k: string, t: string | undefined) => {
127
- statusKey = k;
128
- statusText = t;
129
- },
130
- notify: (s: string) => notifies.push(s),
131
- select: () => {},
132
- confirm: async () => true,
133
- input: async () => "",
134
- setWidget: () => {},
135
- },
136
- mode: "tui" as any,
137
- hasUI: true,
138
- cwd: stateDir,
139
- sessionManager,
140
- modelRegistry: {} as any,
141
- model: undefined,
142
- isIdle: () => true,
143
- isProjectTrusted: () => true,
144
- signal: undefined,
145
- abort: () => {},
146
- hasPendingMessages: () => false,
147
- shutdown: () => {},
148
- getContextUsage: () => ({
149
- tokens: 200000,
150
- contextWindow: 200000,
151
- percent: 100,
152
- }),
153
- // Faithful mock: ctx.compact() starts pi's flow, which fires the
154
- // session_before_compact handler (where WE supply the durable trim).
155
- compact: (opts?: any) => {
156
- compactCalls.push(opts);
157
- const _sbc = handlers["session_before_compact"]; if (_sbc && _sbc.length) {
158
- return _sbc[0](
159
- {
160
- type: "session_before_compact",
161
- reason: "threshold",
162
- willRetry: false,
163
- signal: undefined,
164
- // pi computed the cut honoring anchor floor + tool-pair (PREVENT-PI-002);
165
- // our handler reuses it as firstKeptEntryId.
166
- preparation: {
167
- firstKeptEntryId: "e2",
168
- messagesToSummarize: session.slice(0, 2),
169
- tokensBefore: 500,
170
- },
171
- } as any,
172
- makeCtx(),
173
- );
174
- }
175
- return undefined;
176
- },
177
- getSystemPrompt: () => "system base",
178
- ...over,
179
- } as any;
180
- }
181
-
182
- const pi = {
183
- on: (ev: string, h: Function) => {
184
- if (!handlers[ev]) handlers[ev] = [];
185
- handlers[ev].push(h);
186
- },
187
- registerCommand: (name: string, opts: any) => {
188
- commands[name] = opts;
189
- },
190
- registerTool: () => {},
191
- registerShortcut: () => {},
192
- registerFlag: () => {},
193
- getFlag: () => undefined,
194
- registerMessageRenderer: () => {},
195
- registerEntryRenderer: () => {},
196
- sendMessage: (_m: any) => {},
197
- sendUserMessage: (m: string) => {
198
- sendUserMessages.push(m);
199
- },
200
- appendEntry: (t: string, d: any) => appended.push({ t, d }),
201
- setSessionName: () => {},
202
- getSessionName: () => undefined,
203
- setLabel: () => {},
204
- exec: async () => ({ stdout: "", stderr: "", code: 0 }),
205
- getActiveTools: () => [],
206
- getAllTools: () => [],
207
- setActiveTools: () => {},
208
- getCommands: () => [],
209
- setModel: async () => false,
210
- getThinkingLevel: () => "off" as any,
211
- setThinkingLevel: () => {},
212
- } as any;
213
-
214
- // Import the compiled extension (same dist/extensions dir as this test).
215
- const mod = require("./mega-compact.js") as { default: (p: any) => void };
216
- mod.default(pi);
217
-
218
- return {
219
- stateDir,
220
- handlers,
221
- commands,
222
- appended,
223
- get status() {
224
- return { statusKey, statusText };
225
- },
226
- notifies,
227
- compactCalls,
228
- sendUserMessages,
229
- fire: async (ev: string, event: any, ctx: any) => { let r: any; for (const h of handlers[ev] || []) r = await h(event, ctx); return r; },
230
- ctx: makeCtx,
231
- session,
232
- };
233
- }
234
-
235
- test("auto-trigger (legacy): past threshold persists a chkpt and starts a durable trim via ctx.compact", async () => {
236
- const h = harness();
237
- const messages = h.session;
238
- // The mock session is tiny (~100 tokens). piCompactWouldNoop() would skip
239
- // ctx.compact() for a transcript under pi's keepRecentTokens budget — so
240
- // lower the floor to 0 to simulate a transcript large enough that pi WOULD
241
- // compact (the positive path this test exercises).
242
- // S16: this is the LEGACY path — the default no longer calls ctx.compact()
243
- // (it returns a live-trimmed view instead). Set the legacy flag to exercise
244
- // the v0.4.28 ctx.compact durable-trim flow this test asserts.
245
- process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR = "0";
246
- process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "true";
247
- try {
248
- const ctx = h.ctx({
249
- getContextUsage: () => ({
250
- tokens: 200000,
251
- contextWindow: 200000,
252
- percent: 100,
253
- }),
254
- });
255
- const res = await h.fire("context", { type: "context", messages }, ctx);
256
- // S38.5: the strict race guard (default) defers ctx.compact() via a
257
- // setTimeout(500) re-check. The synchronous return is still undefined
258
- // (no local drop), but ctx.compact() only lands after the timer — wait
259
- // for it before asserting the durable-trim call count.
260
- await new Promise((r) => setTimeout(r, 700));
261
- // L1->L4 ran: a checkpoint was persisted to the SQLite store + a marker entry written.
262
- const { listCheckpoints } = await import("../src/store/sqlite.js");
263
- assert.ok(
264
- listCheckpoints("sess_ext_001", h.stateDir).length > 0,
265
- "checkpoint persisted to local vector db",
266
- );
267
- assert.equal(
268
- h.appended.some((a) => a.t === "mega-compact-marker"),
269
- true,
270
- "marker sentinel appended",
271
- );
272
- // The legacy context handler triggers pi's compaction flow (ctx.compact),
273
- // which calls our session_before_compact handler to supply the DURABLE trim.
274
- assert.equal(
275
- res,
276
- undefined,
277
- "legacy context handler returns nothing (no local drop)",
278
- );
279
- assert.equal(
280
- h.compactCalls.length,
281
- 1,
282
- "ctx.compact() called to start durable trim (legacy path)",
283
- );
284
- // The durable trim was supplied (summary + firstKeptEntryId from pi's prep).
285
- assert.ok(h.compactCalls[0] !== undefined, "compaction flow executed");
286
- } finally {
287
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
288
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
289
- }
290
- });
291
-
292
- test("auto-trigger: skips ctx.compact() when pi would no-op (session too small, legacy path)", async () => {
293
- const h = harness();
294
- const messages = h.session;
295
- // Default floor (20000): the tiny mock transcript is below pi's
296
- // keepRecentTokens budget, so piCompactWouldNoop() must skip ctx.compact()
297
- // rather than surface pi's "Nothing to compact (session too small)" throw.
298
- // S16: exercised under the legacy flag (the default path never calls ctx.compact).
299
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
300
- process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "true";
301
- try {
302
- const ctx = h.ctx({
303
- getContextUsage: () => ({
304
- tokens: 200000,
305
- contextWindow: 200000,
306
- percent: 100,
307
- }),
308
- });
309
- const res = await h.fire("context", { type: "context", messages }, ctx);
310
- assert.equal(
311
- res,
312
- undefined,
313
- "legacy context handler returns nothing (no local drop)",
314
- );
315
- assert.equal(
316
- h.compactCalls.length,
317
- 0,
318
- "ctx.compact() NOT called — pi would no-op",
319
- );
320
- // Our recall checkpoint still persisted (Path A) — the durable trim is the
321
- // only thing skipped; recall is independent of it.
322
- const { listCheckpoints } = await import("../src/store/sqlite.js");
323
- assert.ok(
324
- listCheckpoints("sess_ext_001", h.stateDir).length > 0,
325
- "recall checkpoint still persisted",
326
- );
327
- assert.equal(
328
- h.appended.some((a) => a.t === "mega-compact-marker"),
329
- true,
330
- "marker sentinel still appended",
331
- );
332
- } finally {
333
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
334
- }
335
- });
336
-
337
- test("auto-trigger (S16): trims the live view and does NOT call ctx.compact()", async () => {
338
- const h = harness();
339
- const messages = h.session;
340
- // S16 default: live context-event trim. No legacy flag. Lower the anchor floor
341
- // so the trimmed recent window (4 messages, 2 user) clears the anchor check
342
- // and the live trim actually fires — mirrors how the legacy test lowers the
343
- // durable floor to exercise its positive path.
344
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
345
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
346
- process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES = "1";
347
- try {
348
- const ctx = h.ctx({
349
- getContextUsage: () => ({
350
- tokens: 200000,
351
- contextWindow: 200000,
352
- percent: 100,
353
- }),
354
- });
355
- const res = await h.fire("context", { type: "context", messages }, ctx);
356
- // S16: context handler returns a TRIMMED messages array (live trim), not undefined.
357
- assert.ok(
358
- res && typeof res === "object",
359
- "context handler returns a result object (live trim)",
360
- );
361
- assert.ok(
362
- Array.isArray((res as any).messages),
363
- "result has a trimmed messages array",
364
- );
365
- // The trimmed view starts with the compacted summary (user-role) + is shorter.
366
- assert.ok(
367
- (res as any).messages.length < messages.length,
368
- "trimmed view is shorter than the full session",
369
- );
370
- // S16: ctx.compact() is NEVER called (it would stop the agent).
371
- assert.equal(
372
- h.compactCalls.length,
373
- 0,
374
- "ctx.compact() NOT called — compact-and-continue",
375
- );
376
- // The recall checkpoint is still persisted (the durable value).
377
- const { listCheckpoints } = await import("../src/store/sqlite.js");
378
- assert.ok(
379
- listCheckpoints("sess_ext_001", h.stateDir).length > 0,
380
- "recall checkpoint persisted under live trim",
381
- );
382
- } finally {
383
- delete process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES;
384
- }
385
- });
386
-
387
- test("auto-trigger (S16): does not trim when below the anchor floor (returns undefined, no ctx.compact)", async () => {
388
- const h = harness();
389
- // A session so short that buildLiveTrimmedView's anchor floor can't hold — the
390
- // live trim skips this call (returns undefined, the next context event retries).
391
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
392
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
393
- const shortSession = [h.session[0], h.session[1]]; // one user + one assistant
394
- const ctx = h.ctx({
395
- getContextUsage: () => ({
396
- tokens: 200000,
397
- contextWindow: 200000,
398
- percent: 100,
399
- }),
400
- });
401
- const res = await h.fire(
402
- "context",
403
- { type: "context", messages: shortSession },
404
- ctx,
405
- );
406
- // Either it skipped (undefined) or trimmed safely — but it must never call ctx.compact.
407
- assert.equal(
408
- h.compactCalls.length,
409
- 0,
410
- "ctx.compact() NOT called under live trim (short session)",
411
- );
412
- if (res === undefined) {
413
- // skipped path is fine
414
- assert.ok(
415
- true,
416
- "below anchor floor → no trim this call (retries next event)",
417
- );
418
- }
419
- });
420
-
421
- test("auto-trigger (S16): sendUserMessage resume nudge fires only when idle + queued + not already nudged", async () => {
422
- const h = harness();
423
- // No queued messages → the nudge must NOT fire (the guard prevents busy-loops).
424
- // We assert the extension did not throw and did not push a spurious resume.
425
- const ctx = h.ctx({ isIdle: () => true, hasPendingMessages: () => false });
426
- await h.fire("agent_end", { type: "agent_end", messages: [] }, ctx);
427
- // No throw + no spurious nudge side-effect is the contract; appended stays
428
- // free of any auto "continue" marker when there is no queued work.
429
- assert.equal(
430
- h.appended.some((a) => a.t && /continue/i.test(String(a.d ?? ""))),
431
- false,
432
- "no spurious continue when no queued work",
433
- );
434
- });
435
-
436
- test("auto-trigger (S16): durable trim still happens via pi native auto-compaction (session_before_compact)", async () => {
437
- const h = harness();
438
- // pi's native auto-compaction fires at agent-end with reason "threshold" (the
439
- // CONTINUING path). Our session_before_compact handler must still supply the
440
- // durable trim summary — independent of the live context-event trim.
441
- const prep = {
442
- firstKeptEntryId: "e2",
443
- messagesToSummarize: h.session.slice(0, 4),
444
- tokensBefore: 500,
445
- };
446
- const res = await h.fire(
447
- "session_before_compact",
448
- {
449
- type: "session_before_compact",
450
- reason: "threshold",
451
- willRetry: false,
452
- signal: undefined,
453
- preparation: prep,
454
- } as any,
455
- h.ctx(),
456
- );
457
- assert.ok(
458
- res?.compaction,
459
- "we supply a durable compaction result to pi's native path",
460
- );
461
- assert.ok(
462
- res.compaction.firstKeptEntryId === "e2",
463
- "reuses pi's boundary (PREVENT-PI-002)",
464
- );
465
- assert.ok(res.compaction.summary.length > 0, "summary is non-empty");
466
- });
467
-
468
- test("session_before_compact supplies our durable trim (not pi's summary)", async () => {
469
- const h = harness();
470
- // pi fires session_before_compact with its own computed preparation.
471
- const res = await h.fire(
472
- "session_before_compact",
473
- {
474
- type: "session_before_compact",
475
- reason: "overflow",
476
- willRetry: true,
477
- preparation: {
478
- firstKeptEntryId: "e2",
479
- messagesToSummarize: h.session.slice(0, 2),
480
- tokensBefore: 500,
481
- },
482
- signal: undefined,
483
- } as any,
484
- h.ctx(),
485
- );
486
- assert.ok(res && res.compaction, "returns a compaction result");
487
- assert.equal(
488
- res.compaction.firstKeptEntryId,
489
- "e2",
490
- "reuses pi's cut boundary (PREVENT-PI-002 safe)",
491
- );
492
- assert.ok(
493
- typeof res.compaction.summary === "string" &&
494
- res.compaction.summary.length > 0,
495
- "our summary supplied",
496
- );
497
- assert.ok(res.compaction.tokensBefore >= 0, "tokensBefore reported");
498
- });
499
-
500
- test("session_before_compact supplies a fallback summary when nothing to summarize", async () => {
501
- const h = harness();
502
- // Empty preparation → no messages to summarize (anchor floor protects
503
- // everything). We MUST still supply a compaction (never {}), otherwise pi
504
- // runs its own compact() which throws "Nothing to compact (session too
505
- // small)" and leaves the session stuck with no resume context. The fallback
506
- // records a minimal resume summary so the session always resumes.
507
- const res = await h.fire(
508
- "session_before_compact",
509
- {
510
- type: "session_before_compact",
511
- reason: "threshold",
512
- willRetry: false,
513
- preparation: {
514
- firstKeptEntryId: "e0",
515
- messagesToSummarize: [],
516
- tokensBefore: 0,
517
- },
518
- signal: undefined,
519
- } as any,
520
- h.ctx(),
521
- );
522
- assert.ok(
523
- res && (res as any).compaction,
524
- "fallback compaction supplied (never {})",
525
- );
526
- assert.ok(
527
- (res as any).compaction.summary.includes("context compacted"),
528
- "fallback summary injected so the session resumes",
529
- );
530
- assert.equal(
531
- (res as any).compaction.firstKeptEntryId,
532
- "e0",
533
- "keeps pi's cut point",
534
- );
535
- });
536
-
537
- test("resume auto-inline stages recall into the system prompt", async () => {
538
- const h = harness();
539
- // Seed a checkpoint first (simulate a prior session that compacted).
540
- await h.fire(
541
- "context",
542
- { type: "context", messages: h.session },
543
- h.ctx({
544
- getContextUsage: () => ({
545
- tokens: 200000,
546
- contextWindow: 200000,
547
- percent: 100,
548
- }),
549
- }),
550
- );
551
- // Fresh resume: session_start with reason "resume".
552
- const ctx = h.ctx();
553
- await h.fire(
554
- "session_start",
555
- {
556
- type: "session_start",
557
- reason: "resume",
558
- previousSessionFile: undefined,
559
- } as any,
560
- ctx,
561
- );
562
- // The next before_agent_start must prepend the recalled block.
563
- const res = await h.fire(
564
- "before_agent_start",
565
- {
566
- type: "before_agent_start",
567
- prompt: "base system",
568
- images: undefined,
569
- systemPrompt: "base system",
570
- systemPromptOptions: {},
571
- } as any,
572
- ctx,
573
- );
574
- assert.ok(
575
- res && typeof res.systemPrompt === "string",
576
- "before_agent_start returns a systemPrompt",
577
- );
578
- assert.ok(
579
- res.systemPrompt.includes("Recalled context"),
580
- "recalled block injected into system prompt",
581
- );
582
- });
583
-
584
- test("/recall-context reports and stages the top checkpoint", async () => {
585
- const h = harness();
586
- await h.fire(
587
- "context",
588
- { type: "context", messages: h.session },
589
- h.ctx({
590
- getContextUsage: () => ({
591
- tokens: 200000,
592
- contextWindow: 200000,
593
- percent: 100,
594
- }),
595
- }),
596
- );
597
- const ctx = h.ctx();
598
- await h.commands["mega-recall"].handler("dedupe bug store.ts", ctx);
599
- assert.ok(
600
- h.notifies.some((n) => n.includes("recall staged")),
601
- "command reports staged checkpoints",
602
- );
603
- assert.ok(
604
- h.notifies.some((n) => n.includes("chkpt_")),
605
- "command names the checkpoint",
606
- );
607
- });
608
-
609
- test("/megacompact-status reports live store stats", async () => {
610
- const h = harness();
611
- await h.fire(
612
- "context",
613
- { type: "context", messages: h.session },
614
- h.ctx({
615
- getContextUsage: () => ({
616
- tokens: 200000,
617
- contextWindow: 200000,
618
- percent: 100,
619
- }),
620
- }),
621
- );
622
- const ctx = h.ctx({
623
- getContextUsage: () => ({
624
- tokens: 50000,
625
- contextWindow: 200000,
626
- percent: 25,
627
- }),
628
- });
629
- await h.commands["mega-status"].handler("", ctx);
630
- assert.ok(
631
- h.notifies.some((n) => n.includes("store:") && n.includes("chkpt")),
632
- "status shows checkpoint count",
633
- );
634
- });
635
-
636
- // ---- Model/provider capture (Phase 5b model_snapshots) ----------------------
637
- test("model_select captures model + provider into SQL", async () => {
638
- const h = harness();
639
- const modelCtx = h.ctx({
640
- model: {
641
- id: "claude-opus-4-8",
642
- name: "Claude Opus 4.8",
643
- provider: "anthropic",
644
- contextWindow: 200000,
645
- maxTokens: 32000,
646
- reasoning: false,
647
- cost: { input: 0.000015, output: 0.000075 },
648
- },
649
- modelRegistry: {
650
- getProviderDisplayName: (p: string) =>
651
- p === "anthropic" ? "Anthropic" : p,
652
- },
653
- });
654
- await h.fire("model_select", {}, modelCtx);
655
- const { latestModelSnapshot } = await import("../src/store/sqlite.js");
656
- const snap = latestModelSnapshot(h.stateDir);
657
- assert.ok(snap, "model_snapshots row persisted");
658
- assert.equal(snap!.modelId, "claude-opus-4-8", "correct model id captured");
659
- assert.equal(snap!.provider, "anthropic", "correct provider captured");
660
- assert.equal(
661
- snap!.providerName,
662
- "Anthropic",
663
- "provider display name resolved",
664
- );
665
- assert.equal(snap!.inputRate, 0.000015, "input rate captured");
666
- });
667
-
668
- test("/mega-status surfaces the captured model + provider", async () => {
669
- const h = harness();
670
- const modelCtx = h.ctx({
671
- model: {
672
- id: "claude-opus-4-8",
673
- name: "Claude Opus 4.8",
674
- provider: "anthropic",
675
- contextWindow: 200000,
676
- maxTokens: 32000,
677
- reasoning: false,
678
- cost: { input: 0.000015, output: 0.000075 },
679
- },
680
- modelRegistry: { getProviderDisplayName: () => "Anthropic" },
681
- });
682
- await h.fire("model_select", {}, modelCtx);
683
- await h.fire(
684
- "context",
685
- { type: "context", messages: h.session },
686
- h.ctx({
687
- getContextUsage: () => ({
688
- tokens: 200000,
689
- contextWindow: 200000,
690
- percent: 100,
691
- }),
692
- }),
693
- );
694
- const ctx = h.ctx({
695
- getContextUsage: () => ({
696
- tokens: 50000,
697
- contextWindow: 200000,
698
- percent: 25,
699
- }),
700
- });
701
- await h.commands["mega-status"].handler("", ctx);
702
- assert.ok(
703
- h.notifies.some(
704
- (n) =>
705
- n.includes("🤖 model:") &&
706
- n.includes("Claude Opus 4.8") &&
707
- n.includes("Anthropic"),
708
- ),
709
- "status surfaces captured model + provider",
710
- );
711
- });
712
-
713
- // ---- Named compaction tiers -------------------------------------------------
714
- // low=50k, medium=100k, high=200k, ultra=1M, mega=10M. Driven through the REAL
715
- // loadConfig()/status path by setting MEGACOMPACT_TIER before loading the ext.
716
- // Percentage-based thresholds: tierPct × the model context window. The harness
717
- // getContextUsage below reports contextWindow=2_000_000, so each tier resolves to
718
- // tierPct × 2_000_000 — which fires BELOW pi's native ~80% auto-compaction for
719
- // ANY model size (200k or 1M). Driven through the REAL loadConfig()/status path
720
- // by setting MEGACOMPACT_TIER before loading the ext.
721
- const TIER_CASES: Array<[string, number]> = [
722
- ["low", 1_000_000], // 0.50 × 2_000_000
723
- ["medium", 1_200_000], // 0.60 × 2_000_000
724
- ["high", 1_400_000], // 0.70 × 2_000_000
725
- ["ultra", 1_400_000], // 0.70 × 2_000_000
726
- ["mega", 1_500_000], // 0.75 × 2_000_000
727
- ];
728
- for (const [tier, threshold] of TIER_CASES) {
729
- test(`tier "${tier}" resolves to a ${threshold.toLocaleString()}-token threshold (tierPct × 2M window; live band shown separately)`, async () => {
730
- // Keep tier + keep threshold UNSET so the tier (not an explicit number)
731
- // drives the threshold. harness() would otherwise reset the threshold.
732
- delete process.env.MEGACOMPACT_THRESHOLD_TOKENS;
733
- process.env.MEGACOMPACT_TIER = tier;
734
- const h = harness({ keepTier: true, keepThreshold: true });
735
- // tokens=1 against a 2M window → near-zero pressure → live band "low".
736
- const ctx = h.ctx({
737
- getContextUsage: () => ({
738
- tokens: 1,
739
- contextWindow: 2_000_000,
740
- percent: 0.01,
741
- }),
742
- });
743
- await h.commands["mega-status"].handler("", ctx);
744
- delete process.env.MEGACOMPACT_TIER;
745
- // /mega-status renders threshold with toLocaleString() (thousands commas).
746
- assert.ok(
747
- h.notifies.some(
748
- (n) =>
749
- n.includes(`preset=${tier}`) &&
750
- n.includes(`threshold=${threshold.toLocaleString()}`),
751
- ),
752
- `status should report preset=${tier} threshold=${threshold.toLocaleString()} (tierPct × 2M window)`,
753
- );
754
- // S24: the headline tier is the LIVE pressure band, shown as "tier=low (live)".
755
- assert.ok(
756
- h.notifies.some((n) => n.includes("tier=low (live)")),
757
- "live band reported (low at near-zero pressure)",
758
- );
759
- });
760
- }
761
-
762
- test("explicit MEGACOMPACT_THRESHOLD_TOKENS overrides the tier", async () => {
763
- process.env.MEGACOMPACT_TIER = "mega";
764
- process.env.MEGACOMPACT_THRESHOLD_TOKENS = "777";
765
- const h = harness({ keepTier: true, keepThreshold: true });
766
- const ctx = h.ctx({
767
- getContextUsage: () => ({
768
- tokens: 1,
769
- contextWindow: 2_000_000,
770
- percent: 0.01,
771
- }),
772
- });
773
- await h.commands["mega-status"].handler("", ctx);
774
- delete process.env.MEGACOMPACT_TIER;
775
- assert.ok(
776
- h.notifies.some(
777
- (n) => n.includes("preset=custom") && n.includes("threshold=777"),
778
- ),
779
- "explicit threshold wins over tier (preset=custom)",
780
- );
781
- });
782
-
783
- // ---- S24: memory review tied to pressure / compaction -----------------------
784
- // Build a decision-bearing session large enough to guarantee a real (non-skipped,
785
- // non-deduped) compaction. Each user turn contains a decision phrase
786
- // (/\bactually\b/i, /\bwe (?:use|decided)\b/i) so reviewConversation yields ops.
787
- function decisionSession(): AgentMessage[] {
788
- const out: AgentMessage[] = [];
789
- for (let i = 0; i < 14; i++) {
790
- out.push({
791
- role: "user",
792
- content: `actually we decided to use approach ${i} for module ${i}`,
793
- timestamp: i,
794
- } as unknown as AgentMessage);
795
- out.push({
796
- role: "assistant",
797
- content: [{ type: "toolCall", name: "Edit", id: `c${i}`, arguments: {} }],
798
- api: "anthropic-messages",
799
- provider: "anthropic",
800
- model: "m",
801
- usage: {
802
- inputTokens: 1,
803
- outputTokens: 1,
804
- cacheReadTokens: 0,
805
- cacheWriteTokens: 0,
806
- },
807
- stopReason: "tool_use",
808
- timestamp: i,
809
- } as unknown as AgentMessage);
810
- out.push({
811
- role: "toolResult",
812
- content: [{ type: "text", text: `edited module ${i}` }],
813
- toolCallId: `c${i}`,
814
- toolName: "Edit",
815
- isError: false,
816
- timestamp: i,
817
- } as unknown as AgentMessage);
818
- }
819
- return out;
820
- }
821
-
822
- test("S24: high pressure triggers a memory review on compaction", async () => {
823
- const h = harness();
824
- // Force a real (non-legacy) compaction at full pressure → pressureBand "mega",
825
- // which must fire the shared runMemoryReview on compact (review-on-compact).
826
- process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "false";
827
- try {
828
- const messages = decisionSession();
829
- const ctx = h.ctx({
830
- getContextUsage: () => ({
831
- tokens: 200000,
832
- contextWindow: 200000,
833
- percent: 100,
834
- }),
835
- });
836
- await h.fire("context", { type: "context", messages }, ctx);
837
- // review-on-compact runs as a fire-and-forget async (doCompact is sync), so
838
- // let the microtask/macrotask queue drain before asserting the side effect.
839
- await new Promise((r) => setTimeout(r, 20));
840
- const { listMemories, listCheckpoints } = await import(
841
- "../src/store/sqlite.js"
842
- );
843
- // A checkpoint must have been persisted (proves compaction ran, not skipped).
844
- assert.ok(
845
- listCheckpoints("sess_ext_001", h.stateDir).length > 0,
846
- "checkpoint persisted to local vector db",
847
- );
848
- // The just-compacted region is worth remembering, so durable memories must
849
- // have been written to the SQLite store (review-on-compact path).
850
- const mem = listMemories(null, 50, h.stateDir);
851
- assert.ok(
852
- mem.length > 0,
853
- "memory review wrote durable memories on compact",
854
- );
855
- } finally {
856
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
857
- }
858
- });
859
-
860
- test("S24: /mega-status reports the live pressure band + %", async () => {
861
- const h = harness();
862
- // Populate the runtime's live context first (a context event sets
863
- // lastCtxTokens/lastCtxPercent), then read /mega-status. At 100% usage the live
864
- // band must read "mega" and pressure must report 100%.
865
- const ctx = h.ctx({
866
- getContextUsage: () => ({
867
- tokens: 200000,
868
- contextWindow: 200000,
869
- percent: 100,
870
- }),
871
- });
872
- await h.fire("context", { type: "context", messages: h.session }, ctx);
873
- await h.commands["mega-status"].handler("", ctx);
874
- assert.ok(
875
- h.notifies.some((n) => n.includes("tier=mega (live)")),
876
- "live band reported as mega at 100% pressure",
877
- );
878
- assert.ok(
879
- h.notifies.some((n) => n.includes("pressure=100%")),
880
- "live pressure % reported",
881
- );
882
- });
883
-
884
- // ---- /dashboard commands ----------------------------------------------------
885
- test("/dashboard-status reports no server when pid file missing", async () => {
886
- // Private base so this asserts "no server" on a range nothing else uses,
887
- // not the machine-global 9320 family (which may hold a leftover/production server).
888
- process.env.MEGACOMPACT_DASHBOARD_PORT = "49320";
889
- try {
890
- const h = harness();
891
- const ctx = h.ctx();
892
- await h.commands["mega-dashboard-status"].handler("", ctx);
893
- assert.ok(
894
- h.notifies.some((n) => n.includes("not running")),
895
- "reports no server running",
896
- );
897
- } finally {
898
- delete process.env.MEGACOMPACT_DASHBOARD_PORT;
899
- }
900
- });
901
-
902
- test("/dashboard-stop reports no server when pid file missing", async () => {
903
- const h = harness();
904
- const ctx = h.ctx();
905
- await h.commands["mega-dashboard-stop"].handler("", ctx);
906
- assert.ok(
907
- h.notifies.some((n) => n.includes("no dashboard server running")),
908
- "reports no server",
909
- );
910
- });
911
-
912
- // Skipped: creates a real localhost HTTP server + 10-port scan that hangs the
913
- // isolated test runner (open handle keeps the event loop alive). The two
914
- // /dashboard-*-status/stop tests above cover the no-server paths; the
915
- // positive spawn path is covered by dashboard-server.test.js.
916
- test.skip("/dashboard skips server spawn when already running", async () => {
917
- // Use a private dashboard port base for THIS test's harness + fake server so
918
- // it never races the (parallel, hard-coded-9320) dashboard-server.test.js or
919
- // a leftover production server. Set BEFORE harness() so registerDashboardCommands
920
- // reads our base for findLivePort().
921
- process.env.MEGACOMPACT_DASHBOARD_PORT = "29320";
922
- const h = harness();
923
- const confirms: boolean[] = [];
924
- const livPort = 29320; // inside the harness's private scan range (29320–29329)
925
- const { createServer } = await import("node:http");
926
- const server = createServer((_req, res) => {
927
- res.writeHead(200, { "Content-Type": "application/json" });
928
- res.end(
929
- JSON.stringify({
930
- updatedAt: new Date().toISOString(),
931
- tier: "test",
932
- version: 1,
933
- config: {},
934
- session: {},
935
- context: {},
936
- trigger: {},
937
- store: {},
938
- }),
939
- );
940
- });
941
- await new Promise<void>((r) => server.listen(livPort, "127.0.0.1", r));
942
- const { join: j } = await import("node:path");
943
- const { writeFileSync: wf } = await import("node:fs");
944
- wf(
945
- j(h.stateDir, "port.pid"),
946
- JSON.stringify({ port: livPort, pid: process.pid }),
947
- );
948
-
949
- const ctx = h.ctx({
950
- ui: {
951
- setStatus: () => {},
952
- notify: (s: string) => {
953
- h.notifies.push(s);
954
- },
955
- select: () => {},
956
- confirm: async () => {
957
- confirms.push(true);
958
- return true;
959
- },
960
- input: async () => "",
961
- },
962
- });
963
-
964
- await h.commands["mega-dashboard"].handler("", ctx);
965
- assert.ok(
966
- h.notifies.some((n) => n.includes("already running")),
967
- "reports already running",
968
- );
969
- assert.ok(confirms.length > 0, "confirm dialog was shown");
970
-
971
- await new Promise<void>((r) => server.close(() => r()));
972
- delete process.env.MEGACOMPACT_DASHBOARD_PORT;
973
- });
974
-
975
- test("state snapshot writes dashboard.json after compaction", async () => {
976
- const h = harness();
977
- const ctx = h.ctx({
978
- getContextUsage: () => ({
979
- tokens: 200000,
980
- contextWindow: 200000,
981
- percent: 100,
982
- }),
983
- });
984
- // Fire auto-trigger compaction (context event above 80% threshold)
985
- await h.fire("context", { type: "context", messages: h.session }, ctx);
986
- const { existsSync: ex, readFileSync: rf } = await import("node:fs");
987
- const { join: j } = await import("node:path");
988
- const snapPath = j(h.stateDir, "dashboard.json");
989
- assert.ok(ex(snapPath), "dashboard.json written after compaction");
990
- const snap = JSON.parse(rf(snapPath, "utf-8"));
991
- // Item B: the honest token model is wired — the original dropped region was
992
- // captured (originalTokens > 0), and the saved amount never exceeds the
993
- // original (saved = max(0, original − stored) ≤ original). For this tiny
994
- // harness session the summary can be ≥ the region, so saved may be 0; the
995
- // positive "saved > 0" case with a large region is covered by the
996
- // vectorStore unit tests.
997
- assert.ok(
998
- snap.store.originalTokens > 0,
999
- "snapshot.store.originalTokens captured after compaction",
1000
- );
1001
- assert.ok(
1002
- snap.store.originalTokens >= snap.store.tokensSaved,
1003
- "model invariant: original region >= tokens saved",
1004
- );
1005
- // Item A: crew (live agent) block is present in the dashboard snapshot.
1006
- assert.ok(
1007
- snap.crew && typeof snap.crew.activeAgents === "number",
1008
- "snapshot.crew.activeAgents present",
1009
- );
1010
- });
1011
-
1012
- test("events.log receives compaction events", async () => {
1013
- const h = harness();
1014
- const ctx = h.ctx({
1015
- getContextUsage: () => ({
1016
- tokens: 200000,
1017
- contextWindow: 200000,
1018
- percent: 100,
1019
- }),
1020
- });
1021
- // Fire auto-trigger compaction twice (first fires compaction, second also fires)
1022
- await h.fire("context", { type: "context", messages: h.session }, ctx);
1023
- const { readFileSync: rf, existsSync: ex } = await import("node:fs");
1024
- const { join: j } = await import("node:path");
1025
- const logPath = j(h.stateDir, "events.log");
1026
- if (ex(logPath)) {
1027
- const content = rf(logPath, "utf-8").trim();
1028
- // At minimum, we expect at least one event logged
1029
- assert.ok(content.length > 0, "events.log is non-empty after compaction");
1030
- } else {
1031
- // events.log may not exist if the DashboardEmitter path differs from stateDir;
1032
- // verify dashboard.json was written (proves the post-compact path executed)
1033
- assert.ok(
1034
- ex(j(h.stateDir, "dashboard.json")),
1035
- "dashboard.json proves post-compact ran",
1036
- );
1037
- }
1038
- });
1039
-
1040
- test("S28: length-stop auto-continue nudges once, no ctx.compact on low-pressure length path", async () => {
1041
- const h = harness();
1042
- // Force a low-pressure context so the durable-trim branch (which calls
1043
- // ctx.compact()) is NOT taken; only the length-stop nudge should fire.
1044
- const lowPressureCtx = h.ctx({
1045
- isIdle: () => true,
1046
- hasPendingMessages: () => false,
1047
- getContextUsage: () => ({ tokens: 100, contextWindow: 200000, percent: 0 }),
1048
- });
1049
- // 1) Normal stop: no length flag armed → no nudge.
1050
- await h.fire(
1051
- "turn_end",
1052
- {
1053
- type: "turn_end",
1054
- turnIndex: 1,
1055
- message: { role: "assistant", stopReason: "stop" },
1056
- },
1057
- lowPressureCtx,
1058
- );
1059
- await h.fire(
1060
- "agent_end",
1061
- { type: "agent_end", messages: [] },
1062
- lowPressureCtx,
1063
- );
1064
- assert.equal(h.sendUserMessages.length, 0, "normal stop: no nudge");
1065
- assert.equal(h.compactCalls.length, 0, "normal stop: no ctx.compact");
1066
-
1067
- // 2) Length stop: arms the flag, agent_end fires exactly one continue nudge
1068
- // that references the output-token truncation (not a compaction).
1069
- await h.fire(
1070
- "turn_end",
1071
- {
1072
- type: "turn_end",
1073
- turnIndex: 2,
1074
- message: { role: "assistant", stopReason: "length" },
1075
- },
1076
- lowPressureCtx,
1077
- );
1078
- await h.fire(
1079
- "agent_end",
1080
- { type: "agent_end", messages: [] },
1081
- lowPressureCtx,
1082
- );
1083
- assert.equal(h.sendUserMessages.length, 1, "length stop: exactly one nudge");
1084
- assert.match(
1085
- h.sendUserMessages[0],
1086
- /output-token cap/,
1087
- "length stop: nudge references the output-token truncation",
1088
- );
1089
- assert.equal(
1090
- h.compactCalls.length,
1091
- 0,
1092
- "length path: ctx.compact() NOT called (low pressure)",
1093
- );
1094
-
1095
- // 3) One-shot: a second agent_end without a new length stop must NOT re-nudge.
1096
- await h.fire(
1097
- "agent_end",
1098
- { type: "agent_end", messages: [] },
1099
- lowPressureCtx,
1100
- );
1101
- assert.equal(
1102
- h.sendUserMessages.length,
1103
- 1,
1104
- "one-shot: no second nudge without a new length stop",
1105
- );
1106
- });
1107
-
1108
- test("S28: length-stop auto-continue fires even when config.auto === false (autoContinueLengthStop is the sole gate)", async () => {
1109
- // Disable auto (durable-trim + queued-resume) but keep the length-stop flag on.
1110
- // Set BEFORE harness() loads the compiled extension so loadConfig() picks it up.
1111
- const prevAuto = process.env.MEGACOMPACT_AUTO;
1112
- process.env.MEGACOMPACT_AUTO = "false";
1113
- try {
1114
- // Re-load the extension with the new env so config.auto is false but
1115
- // autoContinueLengthStop stays true (default).
1116
- const h2 = harness();
1117
- const lowPressureCtx = h2.ctx({
1118
- isIdle: () => true,
1119
- hasPendingMessages: () => false,
1120
- getContextUsage: () => ({
1121
- tokens: 100,
1122
- contextWindow: 200000,
1123
- percent: 0,
1124
- }),
1125
- });
1126
- // Length stop arms the flag; agent_end must still nudge despite auto=false.
1127
- await h2.fire(
1128
- "turn_end",
1129
- {
1130
- type: "turn_end",
1131
- turnIndex: 1,
1132
- message: { role: "assistant", stopReason: "length" },
1133
- },
1134
- lowPressureCtx,
1135
- );
1136
- await h2.fire(
1137
- "agent_end",
1138
- { type: "agent_end", messages: [] },
1139
- lowPressureCtx,
1140
- );
1141
- assert.equal(
1142
- h2.sendUserMessages.length,
1143
- 1,
1144
- "auto=false: length stop still nudges",
1145
- );
1146
- assert.match(
1147
- h2.sendUserMessages[0],
1148
- /output-token cap/,
1149
- "auto=false: nudge references the output-token truncation",
1150
- );
1151
- assert.equal(
1152
- h2.compactCalls.length,
1153
- 0,
1154
- "auto=false: ctx.compact() NOT called (auto gates durable-trim)",
1155
- );
1156
- } finally {
1157
- if (prevAuto === undefined) delete process.env.MEGACOMPACT_AUTO;
1158
- else process.env.MEGACOMPACT_AUTO = prevAuto;
1159
- }
1160
- });
1161
-
1162
- // Helper: read <stateDir>/events.log JSONL and return the list of event `type`s.
1163
- // Dashboard.event (extensions/mega-dashboard.ts) appends `{ ts, type, ...data }`
1164
- // per line. Used to assert the S28 length_stop / length_stop_continue dashboard
1165
- // events fire on the right paths (spec acceptance #7; OPEN issue #3).
1166
- function eventTypes(stateDir: string): string[] {
1167
- const { readFileSync: rf, existsSync: ex } =
1168
- require("node:fs") as typeof import("node:fs");
1169
- const { join: j } = require("node:path") as typeof import("node:path");
1170
- const logPath = j(stateDir, "events.log");
1171
- if (!ex(logPath)) return [];
1172
- const content = rf(logPath, "utf-8").trim();
1173
- if (content.length === 0) return [];
1174
- return content
1175
- .split("\n")
1176
- .map((line) => {
1177
- try {
1178
- return JSON.parse(line).type;
1179
- } catch {
1180
- return undefined;
1181
- }
1182
- })
1183
- .filter((t): t is string => typeof t === "string");
1184
- }
1185
-
1186
- test("S28: length_stop + length_stop_continue dashboard events fire on the right paths", async () => {
1187
- const h = harness();
1188
- const lowPressureCtx = h.ctx({
1189
- isIdle: () => true,
1190
- hasPendingMessages: () => false,
1191
- getContextUsage: () => ({ tokens: 100, contextWindow: 200000, percent: 0 }),
1192
- });
1193
- // Normal stop: no length_stop event, no nudge, no length_stop_continue.
1194
- await h.fire(
1195
- "turn_end",
1196
- {
1197
- type: "turn_end",
1198
- turnIndex: 1,
1199
- message: { role: "assistant", stopReason: "stop" },
1200
- },
1201
- lowPressureCtx,
1202
- );
1203
- await h.fire(
1204
- "agent_end",
1205
- { type: "agent_end", messages: [] },
1206
- lowPressureCtx,
1207
- );
1208
- const afterNormal = eventTypes(h.stateDir);
1209
- assert.ok(
1210
- !afterNormal.includes("length_stop"),
1211
- "normal stop: no length_stop dashboard event",
1212
- );
1213
- assert.ok(
1214
- !afterNormal.includes("length_stop_continue"),
1215
- "normal stop: no length_stop_continue dashboard event",
1216
- );
1217
- assert.equal(h.sendUserMessages.length, 0, "normal stop: no nudge");
1218
-
1219
- // Length stop: length_stop fires on turn_end, length_stop_continue on agent_end.
1220
- await h.fire(
1221
- "turn_end",
1222
- {
1223
- type: "turn_end",
1224
- turnIndex: 2,
1225
- message: { role: "assistant", stopReason: "length" },
1226
- },
1227
- lowPressureCtx,
1228
- );
1229
- const afterTurnEnd = eventTypes(h.stateDir);
1230
- assert.ok(
1231
- afterTurnEnd.includes("length_stop"),
1232
- "length stop: length_stop dashboard event fired on turn_end",
1233
- );
1234
- await h.fire(
1235
- "agent_end",
1236
- { type: "agent_end", messages: [] },
1237
- lowPressureCtx,
1238
- );
1239
- const afterAgentEnd = eventTypes(h.stateDir);
1240
- assert.ok(
1241
- afterAgentEnd.includes("length_stop_continue"),
1242
- "length stop: length_stop_continue dashboard event fired on agent_end",
1243
- );
1244
- assert.equal(h.sendUserMessages.length, 1, "length stop: exactly one nudge");
1245
- });
1246
-
1247
- test("S28: non-length stopReasons do not arm the length-stop flag (no length_stop event); S38 owns error/aborted retries", async () => {
1248
- const h = harness();
1249
- const lowPressureCtx = h.ctx({
1250
- isIdle: () => true,
1251
- hasPendingMessages: () => false,
1252
- getContextUsage: () => ({ tokens: 100, contextWindow: 200000, percent: 0 }),
1253
- });
1254
- // S28 contract: every non-length StopReason must leave lengthStopPending unset,
1255
- // so no length_stop event fires and no length-stop resume nudge is armed.
1256
- // (S38 separately owns error/aborted — it WILL fire error-retry nudges for those;
1257
- // this test only asserts the S28 length-stop flag is not armed.)
1258
- let transientRetries = 0;
1259
- for (const stopReason of ["tool_use", "error", "aborted"] as const) {
1260
- const before = h.sendUserMessages.length;
1261
- await h.fire(
1262
- "turn_end",
1263
- {
1264
- type: "turn_end",
1265
- turnIndex: 1,
1266
- message: { role: "assistant", stopReason },
1267
- },
1268
- lowPressureCtx,
1269
- );
1270
- await h.fire(
1271
- "agent_end",
1272
- { type: "agent_end", messages: [] },
1273
- lowPressureCtx,
1274
- );
1275
- // tool_use is success (0 nudges); error/aborted are transient under S38 (1 each).
1276
- if (stopReason === "tool_use") {
1277
- assert.equal(
1278
- h.sendUserMessages.length - before,
1279
- 0,
1280
- "tool_use (success): no nudge",
1281
- );
1282
- } else {
1283
- transientRetries += h.sendUserMessages.length - before;
1284
- }
1285
- }
1286
- // error + aborted each produced one S38 transient retry nudge (2 total).
1287
- assert.equal(
1288
- transientRetries,
1289
- 2,
1290
- "S38 owns error/aborted: 1 transient retry each",
1291
- );
1292
- assert.ok(
1293
- !eventTypes(h.stateDir).includes("length_stop"),
1294
- "non-length stopReasons: no length_stop dashboard event",
1295
- );
1296
- });
1297
-
1298
- // ---- S29: percent-based auto-compact trigger (gate on context %, not tokens) -
1299
- // The context-handler gate now fires on pct/100 >= (autoPctTrigger ?? tierPct)
1300
- // for tiered configs, with a token FALLBACK when pct is null. `custom` keeps the
1301
- // absolute token gate. These are the first tests to drive a `context` event
1302
- // on a tiered config (the default harness forces custom via THRESHOLD_TOKENS=50).
1303
-
1304
- /** S29 tiered-config helper: tiered (not custom), low tier (tierPct 0.5), with
1305
- * the legacy durable-trim flag off + anchor floor lowered so the live trim
1306
- * returns a trimmed view (mirrors the S16 live-trim test setup at ~line 329). */
1307
- function s29TieredCtx(
1308
- h: ReturnType<typeof harness>,
1309
- usage: { tokens: number; contextWindow: number; percent: number | null },
1310
- ) {
1311
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
1312
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
1313
- process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES = "1";
1314
- return h.ctx({
1315
- isIdle: () => true,
1316
- hasPendingMessages: () => false,
1317
- getContextUsage: () => usage as any,
1318
- });
1319
- }
1320
-
1321
- test("S29: percent gate fires when tokens under-report (tiered low, percent 55, tokens 10)", async () => {
1322
- process.env.MEGACOMPACT_TIER = "low";
1323
- delete process.env.MEGACOMPACT_THRESHOLD_TOKENS;
1324
- delete process.env.MEGACOMPACT_AUTO_PCT_TRIGGER;
1325
- try {
1326
- const h = harness({ keepTier: true, keepThreshold: true });
1327
- // tokens=10 (under the 0.5×10000=5000 token gate), percent=55 (>= 0.5).
1328
- // The OLD token-only gate would return (10 < 5000) → no trim. The S29
1329
- // percent gate (0.55 >= 0.5) fires → live trim returns a trimmed view.
1330
- const ctx = s29TieredCtx(h, {
1331
- tokens: 10,
1332
- contextWindow: 10000,
1333
- percent: 55,
1334
- });
1335
- const res = await h.fire(
1336
- "context",
1337
- { type: "context", messages: h.session },
1338
- ctx,
1339
- );
1340
- assert.ok(
1341
- res && typeof res === "object",
1342
- "percent gate: live trim returned a result object",
1343
- );
1344
- assert.ok(
1345
- Array.isArray((res as any).messages),
1346
- "percent gate: result has a trimmed messages array",
1347
- );
1348
- assert.ok(
1349
- (res as any).messages.length < h.session.length,
1350
- "percent gate: trimmed view is shorter than the full session",
1351
- );
1352
- assert.equal(
1353
- h.compactCalls.length,
1354
- 0,
1355
- "percent gate: live trim, no ctx.compact()",
1356
- );
1357
-
1358
- // Control: percent 40 (< 0.5) → no trim, even with the same under-reported tokens.
1359
- const h2 = harness({ keepTier: true, keepThreshold: true });
1360
- const ctx2 = s29TieredCtx(h2, {
1361
- tokens: 10,
1362
- contextWindow: 10000,
1363
- percent: 40,
1364
- });
1365
- const res2 = await h2.fire(
1366
- "context",
1367
- { type: "context", messages: h2.session },
1368
- ctx2,
1369
- );
1370
- assert.ok(
1371
- !(
1372
- res2 &&
1373
- typeof res2 === "object" &&
1374
- Array.isArray((res2 as any).messages)
1375
- ),
1376
- "percent below fire point: no trim (token count 10 is also below the token gate)",
1377
- );
1378
- } finally {
1379
- delete process.env.MEGACOMPACT_TIER;
1380
- delete process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES;
1381
- }
1382
- });
1383
-
1384
- test("S29: MEGACOMPACT_AUTO_PCT_TRIGGER overrides the tier fire point (0.85)", async () => {
1385
- process.env.MEGACOMPACT_TIER = "low"; // tierPct 0.5
1386
- process.env.MEGACOMPACT_AUTO_PCT_TRIGGER = "0.85";
1387
- delete process.env.MEGACOMPACT_THRESHOLD_TOKENS;
1388
- try {
1389
- // percent 80 < 0.85 → no trim.
1390
- const h = harness({ keepTier: true, keepThreshold: true });
1391
- const ctx80 = s29TieredCtx(h, {
1392
- tokens: 10,
1393
- contextWindow: 10000,
1394
- percent: 80,
1395
- });
1396
- const res80 = await h.fire(
1397
- "context",
1398
- { type: "context", messages: h.session },
1399
- ctx80,
1400
- );
1401
- assert.ok(
1402
- !(
1403
- res80 &&
1404
- typeof res80 === "object" &&
1405
- Array.isArray((res80 as any).messages)
1406
- ),
1407
- "override 0.85: percent 80 does NOT trim (below the override fire point)",
1408
- );
1409
-
1410
- // percent 90 >= 0.85 → trim fires (despite the tier's own 0.5 fire point).
1411
- const h2 = harness({ keepTier: true, keepThreshold: true });
1412
- const ctx90 = s29TieredCtx(h2, {
1413
- tokens: 10,
1414
- contextWindow: 10000,
1415
- percent: 90,
1416
- });
1417
- const res90 = await h2.fire(
1418
- "context",
1419
- { type: "context", messages: h2.session },
1420
- ctx90,
1421
- );
1422
- assert.ok(
1423
- res90 &&
1424
- Array.isArray((res90 as any).messages) &&
1425
- (res90 as any).messages.length < h2.session.length,
1426
- "override 0.85: percent 90 DOES trim (above the override fire point)",
1427
- );
1428
- } finally {
1429
- delete process.env.MEGACOMPACT_TIER;
1430
- delete process.env.MEGACOMPACT_AUTO_PCT_TRIGGER;
1431
- delete process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES;
1432
- }
1433
- });
1434
-
1435
- test("S29: custom tier keeps the absolute token gate (percent 40 but tokens 100 >= 50)", async () => {
1436
- // MEGACOMPACT_THRESHOLD_TOKENS → custom (tierPct null) → token gate, percent ignored.
1437
- process.env.MEGACOMPACT_THRESHOLD_TOKENS = "50";
1438
- delete process.env.MEGACOMPACT_TIER;
1439
- delete process.env.MEGACOMPACT_AUTO_PCT_TRIGGER;
1440
- try {
1441
- const h = harness({ keepTier: true, keepThreshold: true });
1442
- // percent 40 (low) BUT tokens 100 >= 50 threshold → custom token gate fires.
1443
- const ctx = s29TieredCtx(h, {
1444
- tokens: 100,
1445
- contextWindow: 10000,
1446
- percent: 40,
1447
- });
1448
- const res = await h.fire(
1449
- "context",
1450
- { type: "context", messages: h.session },
1451
- ctx,
1452
- );
1453
- assert.ok(
1454
- res &&
1455
- Array.isArray((res as any).messages) &&
1456
- (res as any).messages.length < h.session.length,
1457
- "custom tier: token gate fires (tokens 100 >= 50) despite low percent 40",
1458
- );
1459
- } finally {
1460
- delete process.env.MEGACOMPACT_THRESHOLD_TOKENS;
1461
- delete process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES;
1462
- }
1463
- });
1464
-
1465
- test("S29: tiered config with pct==null falls back to the token gate (not skipped)", async () => {
1466
- // The regression guard for the audit finding: a percent-ONLY gate would skip
1467
- // compaction when percent is unreported. S29 falls back to the token gate
1468
- // (S27 boot-fallback guarantee). tiered low: effectiveThreshold = 0.5×10000 = 5000;
1469
- // tokens 6000 >= 5000 → token fallback fires.
1470
- process.env.MEGACOMPACT_TIER = "low";
1471
- delete process.env.MEGACOMPACT_THRESHOLD_TOKENS;
1472
- delete process.env.MEGACOMPACT_AUTO_PCT_TRIGGER;
1473
- try {
1474
- const h = harness({ keepTier: true, keepThreshold: true });
1475
- const ctx = s29TieredCtx(h, {
1476
- tokens: 6000,
1477
- contextWindow: 10000,
1478
- percent: null,
1479
- });
1480
- const res = await h.fire(
1481
- "context",
1482
- { type: "context", messages: h.session },
1483
- ctx,
1484
- );
1485
- assert.ok(
1486
- res &&
1487
- Array.isArray((res as any).messages) &&
1488
- (res as any).messages.length < h.session.length,
1489
- "pct==null on tiered: token fallback fires (NOT skipped) — S27 boot-fallback preserved",
1490
- );
1491
- } finally {
1492
- delete process.env.MEGACOMPACT_TIER;
1493
- delete process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES;
1494
- }
1495
- });
1496
-
1497
- test("cleanup", async () => {
1498
- // Terminate the global PGlite cross-repo index (WASM worker thread) so the
1499
- // test process can exit. Without this, node --test never returns even though
1500
- // every test passed — the leaked worker keeps the event loop alive.
1501
- // Race with a timeout to prevent 40-min hangs if PGlite WASM close stalls.
1502
- try {
1503
- await Promise.race([
1504
- closeVectorIndex(),
1505
- new Promise((r) => setTimeout(r, 3000)),
1506
- ]);
1507
- } catch { /* ignore */ }
1508
- rmSync(baseTmp, { recursive: true, force: true });
1509
- });