@remnic/core 10.0.69 → 10.0.70

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 (349) hide show
  1. package/dist/access-admin-ops-surface.js +80 -77
  2. package/dist/access-authorization-probe.js +81 -78
  3. package/dist/access-boundary.js +80 -77
  4. package/dist/access-cli.js +194 -191
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +80 -77
  7. package/dist/access-http-briefing.js +81 -78
  8. package/dist/access-http-query.js +82 -79
  9. package/dist/access-http.js +104 -101
  10. package/dist/access-identity-continuity-surface.js +80 -77
  11. package/dist/access-lcm-surface.js +80 -77
  12. package/dist/access-mcp.js +93 -90
  13. package/dist/access-namespace-preflight.js +80 -77
  14. package/dist/access-observe-lcm-archive.d.ts +5 -0
  15. package/dist/access-observe-lcm-archive.d.ts.map +1 -0
  16. package/dist/access-observe-lcm-archive.js +9 -0
  17. package/dist/access-observe-objective-state.d.ts +8 -0
  18. package/dist/access-observe-objective-state.d.ts.map +1 -0
  19. package/dist/access-observe-objective-state.js +17 -0
  20. package/dist/access-observe-stage-timing.d.ts +11 -0
  21. package/dist/access-observe-stage-timing.d.ts.map +1 -0
  22. package/dist/access-observe-stage-timing.js +9 -0
  23. package/dist/access-observe-stage-timing.js.map +1 -0
  24. package/dist/access-observe-write-category.js +13 -13
  25. package/dist/access-observe-write-surface.d.ts.map +1 -1
  26. package/dist/access-observe-write-surface.js +80 -77
  27. package/dist/access-offline-manifest.js +8 -8
  28. package/dist/access-operations-batch.js +83 -80
  29. package/dist/access-operations.js +90 -87
  30. package/dist/access-recall-concurrency.js +80 -77
  31. package/dist/access-recall-response.js +80 -77
  32. package/dist/access-recall-surface.js +80 -77
  33. package/dist/access-schema.js +12 -12
  34. package/dist/access-service.d.ts +1 -0
  35. package/dist/access-service.d.ts.map +1 -1
  36. package/dist/access-service.js +80 -77
  37. package/dist/access-surface-catalog.js +82 -79
  38. package/dist/access-surface-catalog.js.map +1 -1
  39. package/dist/active-recall.js +24 -24
  40. package/dist/adapters/index.js +4 -4
  41. package/dist/adapters/registry.js +2 -2
  42. package/dist/briefing.js +15 -15
  43. package/dist/buffer.js +8 -8
  44. package/dist/calibration.js +4 -4
  45. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  46. package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
  47. package/dist/causal-behavior.js +2 -2
  48. package/dist/causal-chain.js +2 -2
  49. package/dist/causal-consolidation.js +20 -20
  50. package/dist/causal-retrieval.js +2 -2
  51. package/dist/causal-trajectory-graph.js +3 -3
  52. package/dist/causal-trajectory.js +1 -1
  53. package/dist/{chunk-NCZGGGWI.js → chunk-2BBMCMJS.js} +12 -12
  54. package/dist/{chunk-UVVFPCPX.js → chunk-2GV24KFD.js} +2 -2
  55. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  56. package/dist/{chunk-QDZWQBLU.js → chunk-2R7OCBRR.js} +2 -2
  57. package/dist/{chunk-U2CAGMUD.js → chunk-2YPIFJHL.js} +2 -2
  58. package/dist/{chunk-4SOWMA55.js → chunk-3AASGFR4.js} +3 -3
  59. package/dist/{chunk-CI3MAAIY.js → chunk-3F7N4I5I.js} +2 -2
  60. package/dist/{chunk-PLCYKAWH.js → chunk-3ICAWZUU.js} +4 -4
  61. package/dist/{chunk-B4DXREWC.js → chunk-3W37NITP.js} +2 -2
  62. package/dist/{chunk-R4ZHF4K2.js → chunk-43FLJLT2.js} +4 -4
  63. package/dist/{chunk-RLVI2M5Q.js → chunk-4BOBW6H4.js} +2 -2
  64. package/dist/{chunk-HXFIILGF.js → chunk-4RASUAN3.js} +4 -4
  65. package/dist/{chunk-JLMOH5JA.js → chunk-5FMGPV34.js} +2 -2
  66. package/dist/{chunk-EI6M5NVK.js → chunk-5JBVPJCT.js} +2 -2
  67. package/dist/{chunk-74AY3S7U.js → chunk-5RW247F5.js} +10 -10
  68. package/dist/{chunk-P4TV3TQ2.js → chunk-5VQSKBNF.js} +5 -5
  69. package/dist/{chunk-H5DIRCZP.js → chunk-64U42JL5.js} +18 -18
  70. package/dist/{chunk-4XINO77L.js → chunk-66JOLMWS.js} +2 -2
  71. package/dist/{chunk-WHROVQ5Q.js → chunk-66PEACZP.js} +3 -3
  72. package/dist/{chunk-GKL6QYZO.js → chunk-6L63IN3M.js} +1 -1
  73. package/dist/{chunk-Y3XPSGNF.js → chunk-6QS5NQJH.js} +2 -2
  74. package/dist/{chunk-KOLJJN5G.js → chunk-AQNHEDXP.js} +11 -4
  75. package/dist/chunk-AQNHEDXP.js.map +1 -0
  76. package/dist/{chunk-6WKWL53V.js → chunk-B7YAIHAV.js} +127 -121
  77. package/dist/chunk-B7YAIHAV.js.map +1 -0
  78. package/dist/{chunk-M4IEZGCK.js → chunk-BFBS7OFQ.js} +7 -7
  79. package/dist/{chunk-4PG5R6BJ.js → chunk-BNU5TQLR.js} +2 -2
  80. package/dist/{chunk-B3MWELZ3.js → chunk-BW33YTF3.js} +2 -2
  81. package/dist/{chunk-H6VJSITJ.js → chunk-CIL4GHMD.js} +3 -3
  82. package/dist/{chunk-5UM4H64E.js → chunk-D3AGBN6G.js} +2 -2
  83. package/dist/{chunk-IG3EZ3TF.js → chunk-DM3UZUCN.js} +2 -2
  84. package/dist/{chunk-PMDOGXA2.js → chunk-EF3HNVL4.js} +2 -2
  85. package/dist/{chunk-TVE7W67N.js → chunk-EIZ4MK33.js} +2 -2
  86. package/dist/{chunk-AUFPPBC3.js → chunk-ENPJX3BB.js} +3 -3
  87. package/dist/{chunk-63A2BU2B.js → chunk-F4ZHKDBW.js} +5 -5
  88. package/dist/{chunk-3LBDEGQA.js → chunk-FDUCIUQ5.js} +2 -2
  89. package/dist/{chunk-CTH2CQCB.js → chunk-FM6UKZAE.js} +2 -2
  90. package/dist/{chunk-XVZA6VXC.js → chunk-FZVROZKC.js} +4 -4
  91. package/dist/{chunk-IAGVM7EY.js → chunk-GMSRLDVR.js} +2 -2
  92. package/dist/{chunk-HPB5TF55.js → chunk-GT4G2S2Y.js} +3 -3
  93. package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
  94. package/dist/{chunk-5MGKGEL7.js → chunk-HNF7IMYQ.js} +2 -2
  95. package/dist/{chunk-W7K5BP3M.js → chunk-HQPFACXO.js} +2 -2
  96. package/dist/{chunk-TECK5IVY.js → chunk-HZNURRDS.js} +2 -2
  97. package/dist/{chunk-SXTVZ6JL.js → chunk-I6HRHJRA.js} +2 -2
  98. package/dist/{chunk-NHWDTOBN.js → chunk-ILD7N3XC.js} +20 -20
  99. package/dist/{chunk-4NV7O76Q.js → chunk-J5TVHCDG.js} +2 -2
  100. package/dist/{chunk-AL3TCNSJ.js → chunk-J6GZ2254.js} +4 -4
  101. package/dist/{chunk-OH55F76C.js → chunk-JEX2JL3U.js} +2 -2
  102. package/dist/{chunk-AVGDYYMA.js → chunk-JGQEF33K.js} +3 -3
  103. package/dist/{chunk-6P7U3HWG.js → chunk-K36RMINQ.js} +3 -3
  104. package/dist/{chunk-UW6LBFYN.js → chunk-K47G2CBP.js} +6 -6
  105. package/dist/{chunk-3RHU5MD7.js → chunk-K7LLGCPH.js} +2 -2
  106. package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
  107. package/dist/chunk-L3E7MLMK.js +20 -0
  108. package/dist/chunk-L3E7MLMK.js.map +1 -0
  109. package/dist/{chunk-R2BIVU2Z.js → chunk-LTST4R7U.js} +2 -2
  110. package/dist/{chunk-IZBIDPMY.js → chunk-LYTKNK5O.js} +2 -2
  111. package/dist/{chunk-HH73M65H.js → chunk-MCFI6G7U.js} +3 -3
  112. package/dist/{chunk-V6SBA5O7.js → chunk-MFKBYXBY.js} +5 -5
  113. package/dist/{chunk-AYJQ644F.js → chunk-MK7HXGM6.js} +2 -2
  114. package/dist/{chunk-MOIJWWNA.js → chunk-MKMIY3E7.js} +13 -13
  115. package/dist/{chunk-EXQJMFRF.js → chunk-MRN2MKB7.js} +2 -2
  116. package/dist/{chunk-W26D2USW.js → chunk-MUH7RR6T.js} +6 -6
  117. package/dist/{chunk-64JLBFCQ.js → chunk-N7NAGECH.js} +2 -2
  118. package/dist/{chunk-75DCOS6I.js → chunk-OAKOAOEG.js} +67 -67
  119. package/dist/{chunk-DDBB55L2.js → chunk-OAQO5AZJ.js} +121 -121
  120. package/dist/{chunk-OPFVXM2D.js → chunk-OX6GY22Q.js} +2 -2
  121. package/dist/chunk-QCFZDYYG.js +34 -0
  122. package/dist/chunk-QCFZDYYG.js.map +1 -0
  123. package/dist/{chunk-MMCO4SRG.js → chunk-QNN4FYBD.js} +19 -11
  124. package/dist/chunk-QNN4FYBD.js.map +1 -0
  125. package/dist/{chunk-XWE3EOWX.js → chunk-RZED7FAU.js} +2 -2
  126. package/dist/{chunk-YSATDEBY.js → chunk-SBRUTD5O.js} +5 -5
  127. package/dist/{chunk-WVP2I6C5.js → chunk-SDL22RRW.js} +50 -11
  128. package/dist/chunk-SDL22RRW.js.map +1 -0
  129. package/dist/{chunk-ZORS5BG5.js → chunk-SQO5SEKA.js} +10 -10
  130. package/dist/{chunk-PRKNAIGZ.js → chunk-T6T2RMWW.js} +5 -5
  131. package/dist/{chunk-O5ZINWW3.js → chunk-THFSCUFC.js} +2 -2
  132. package/dist/{chunk-CIEEYVUB.js → chunk-TPX7XXY4.js} +2 -2
  133. package/dist/{chunk-OCK4D5PC.js → chunk-TSZP7GGJ.js} +2 -2
  134. package/dist/{chunk-L334PSLW.js → chunk-U2A56LLF.js} +8 -8
  135. package/dist/{chunk-TGQW3AMK.js → chunk-UIFWZMO7.js} +23 -23
  136. package/dist/{chunk-LN4X2NDA.js → chunk-UJ2FP2RV.js} +10 -10
  137. package/dist/{chunk-4MPAKYZU.js → chunk-VDYRXI3E.js} +5 -5
  138. package/dist/{chunk-IHCXMYSE.js → chunk-VPEIWMUT.js} +2 -2
  139. package/dist/{chunk-ELHJK7LM.js → chunk-WBN66HPI.js} +6 -6
  140. package/dist/{chunk-BJLCV2P3.js → chunk-WGRAB3KB.js} +2 -2
  141. package/dist/{chunk-IVUVQPTQ.js → chunk-WIW4LOZL.js} +2 -2
  142. package/dist/{chunk-5KDDAQ6K.js → chunk-WN2DASTS.js} +10 -10
  143. package/dist/chunk-WZU5AGO6.js +38 -0
  144. package/dist/chunk-WZU5AGO6.js.map +1 -0
  145. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  146. package/dist/{chunk-5Y6VA6X2.js → chunk-XDUP6SWG.js} +2 -2
  147. package/dist/{chunk-GHHQLSSY.js → chunk-XR4QR3JT.js} +3 -3
  148. package/dist/{chunk-64S6X4IU.js → chunk-XTN54NML.js} +4 -4
  149. package/dist/{chunk-QGFR73SN.js → chunk-YIG4HPY4.js} +2 -2
  150. package/dist/{chunk-UYUFDTIX.js → chunk-YIXWY4OZ.js} +2 -2
  151. package/dist/{chunk-GJ4HU6LK.js → chunk-YLMTFRZR.js} +2 -2
  152. package/dist/{chunk-GGD2YDS6.js → chunk-YRMSHA7H.js} +2 -2
  153. package/dist/{chunk-Y7427SL7.js → chunk-ZG4GNPRE.js} +216 -50
  154. package/dist/chunk-ZG4GNPRE.js.map +1 -0
  155. package/dist/{chunk-KZZ5EZ7O.js → chunk-ZS5LREUZ.js} +2 -2
  156. package/dist/{chunk-JLGBJ4AX.js → chunk-ZYEWTVNQ.js} +10 -10
  157. package/dist/cli.js +131 -128
  158. package/dist/coding/export-okf-codegraph.js +14 -14
  159. package/dist/coding/pre-action-gate.js +2 -2
  160. package/dist/compounding/engine.js +16 -16
  161. package/dist/config.js +24 -24
  162. package/dist/connectors/codex-materialize-runner.js +17 -17
  163. package/dist/connectors/codex-materialize.js +2 -2
  164. package/dist/connectors/index.js +18 -18
  165. package/dist/consolidation-provenance-check.js +2 -2
  166. package/dist/convergence-refresh.js +18 -18
  167. package/dist/corpus-watermark.js +18 -18
  168. package/dist/deep-recall-http-glue.js +13 -13
  169. package/dist/deep-recall.js +2 -2
  170. package/dist/embedding-fallback.js +2 -2
  171. package/dist/entity-retrieval.js +18 -18
  172. package/dist/event-order-recall.js +2 -2
  173. package/dist/external-wiki-access.js +81 -78
  174. package/dist/extraction-span-materialize.js +2 -2
  175. package/dist/extraction.js +30 -30
  176. package/dist/{graph-edge-decay-OEX5OGMI.js → graph-edge-decay-CXTNJ7IQ.js} +6 -6
  177. package/dist/graph-edge-decay-CXTNJ7IQ.js.map +1 -0
  178. package/dist/graph-jsonl.js +4 -4
  179. package/dist/graph-snapshot.js +4 -4
  180. package/dist/graph.js +3 -3
  181. package/dist/harmonic-retrieval.js +15 -15
  182. package/dist/index.js +279 -276
  183. package/dist/lcm/archive.d.ts +12 -2
  184. package/dist/lcm/archive.d.ts.map +1 -1
  185. package/dist/lcm/archive.js +2 -2
  186. package/dist/lcm/engine.d.ts +59 -8
  187. package/dist/lcm/engine.d.ts.map +1 -1
  188. package/dist/lcm/engine.js +6 -6
  189. package/dist/lcm/index.js +16 -16
  190. package/dist/lcm/queue.d.ts +7 -3
  191. package/dist/lcm/queue.d.ts.map +1 -1
  192. package/dist/lcm/queue.js +1 -1
  193. package/dist/lcm/summarizer.d.ts +8 -1
  194. package/dist/lcm/summarizer.d.ts.map +1 -1
  195. package/dist/lcm/summarizer.js +3 -3
  196. package/dist/local-llm.js +4 -4
  197. package/dist/location/index.js +17 -17
  198. package/dist/maintenance/memory-governance.js +16 -16
  199. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -15
  200. package/dist/maintenance/rebuild-memory-projection-runner.js +18 -18
  201. package/dist/maintenance/rebuild-memory-projection.js +17 -17
  202. package/dist/memory-browse-operations.js +81 -78
  203. package/dist/memory-browse.js +6 -6
  204. package/dist/namespaces/migrate.js +16 -16
  205. package/dist/namespaces/search.js +7 -7
  206. package/dist/namespaces/storage.js +15 -15
  207. package/dist/offline-sync-changeset-generations.js +5 -5
  208. package/dist/offline-sync-embedding-generation.js +4 -4
  209. package/dist/offline-sync-snapshot-stream.js +7 -7
  210. package/dist/offline-sync.js +9 -9
  211. package/dist/operator-doctor-corpus.js +19 -19
  212. package/dist/operator-toolkit.js +39 -39
  213. package/dist/orchestration/buffer-drain-runner.js +194 -191
  214. package/dist/orchestration/buffer-drain-runner.js.map +1 -1
  215. package/dist/orchestration/maintenance.js +31 -31
  216. package/dist/orchestrator.js +195 -192
  217. package/dist/qmd.js +4 -4
  218. package/dist/recall-navigation-operations.js +81 -78
  219. package/dist/recall-navigation.js +80 -77
  220. package/dist/recall-state-view-admission.js +6 -6
  221. package/dist/recall-why-service.js +6 -6
  222. package/dist/resume-bundles.js +27 -27
  223. package/dist/search/factory.js +6 -6
  224. package/dist/search/index.js +12 -12
  225. package/dist/semantic-consolidation.js +23 -23
  226. package/dist/semantic-rule-promotion.js +15 -15
  227. package/dist/semantic-rule-verifier.js +15 -15
  228. package/dist/source-agent-qualifier.js +22 -22
  229. package/dist/standup/build.js +15 -15
  230. package/dist/storage.js +14 -14
  231. package/dist/summarizer.js +6 -6
  232. package/dist/support-passport/index.js +83 -80
  233. package/dist/transfer/backup.js +2 -2
  234. package/dist/transfer/capsule-export.js +2 -2
  235. package/dist/transfer/capsule-import.js +2 -2
  236. package/dist/transfer/export-okf.js +15 -15
  237. package/dist/verified-recall.js +15 -15
  238. package/dist/who-knows-cli.js +16 -16
  239. package/dist/who-knows.js +15 -15
  240. package/package.json +2 -2
  241. package/src/access-observe-lcm-archive.ts +30 -0
  242. package/src/access-observe-lcm-fail-closed.test.ts +118 -0
  243. package/src/access-observe-objective-state.ts +40 -0
  244. package/src/access-observe-stage-timing.test.ts +246 -0
  245. package/src/access-observe-stage-timing.ts +58 -0
  246. package/src/access-observe-write-surface.ts +45 -45
  247. package/src/access-service.ts +1 -0
  248. package/src/lcm/archive.ts +59 -7
  249. package/src/lcm/engine.ts +256 -53
  250. package/src/lcm/queue.test.ts +32 -14
  251. package/src/lcm/queue.ts +24 -16
  252. package/src/lcm/summarizer.ts +15 -2
  253. package/src/lcm-durability.test.ts +856 -0
  254. package/src/lcm-engine.test.ts +12 -3
  255. package/dist/chunk-6WKWL53V.js.map +0 -1
  256. package/dist/chunk-KOLJJN5G.js.map +0 -1
  257. package/dist/chunk-MMCO4SRG.js.map +0 -1
  258. package/dist/chunk-WVP2I6C5.js.map +0 -1
  259. package/dist/chunk-Y7427SL7.js.map +0 -1
  260. /package/dist/{capsule-crypto-GWVG7LGC.js.map → access-observe-lcm-archive.js.map} +0 -0
  261. /package/dist/{graph-edge-decay-OEX5OGMI.js.map → access-observe-objective-state.js.map} +0 -0
  262. /package/dist/{chunk-NCZGGGWI.js.map → chunk-2BBMCMJS.js.map} +0 -0
  263. /package/dist/{chunk-UVVFPCPX.js.map → chunk-2GV24KFD.js.map} +0 -0
  264. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  265. /package/dist/{chunk-QDZWQBLU.js.map → chunk-2R7OCBRR.js.map} +0 -0
  266. /package/dist/{chunk-U2CAGMUD.js.map → chunk-2YPIFJHL.js.map} +0 -0
  267. /package/dist/{chunk-4SOWMA55.js.map → chunk-3AASGFR4.js.map} +0 -0
  268. /package/dist/{chunk-CI3MAAIY.js.map → chunk-3F7N4I5I.js.map} +0 -0
  269. /package/dist/{chunk-PLCYKAWH.js.map → chunk-3ICAWZUU.js.map} +0 -0
  270. /package/dist/{chunk-B4DXREWC.js.map → chunk-3W37NITP.js.map} +0 -0
  271. /package/dist/{chunk-R4ZHF4K2.js.map → chunk-43FLJLT2.js.map} +0 -0
  272. /package/dist/{chunk-RLVI2M5Q.js.map → chunk-4BOBW6H4.js.map} +0 -0
  273. /package/dist/{chunk-HXFIILGF.js.map → chunk-4RASUAN3.js.map} +0 -0
  274. /package/dist/{chunk-JLMOH5JA.js.map → chunk-5FMGPV34.js.map} +0 -0
  275. /package/dist/{chunk-EI6M5NVK.js.map → chunk-5JBVPJCT.js.map} +0 -0
  276. /package/dist/{chunk-74AY3S7U.js.map → chunk-5RW247F5.js.map} +0 -0
  277. /package/dist/{chunk-P4TV3TQ2.js.map → chunk-5VQSKBNF.js.map} +0 -0
  278. /package/dist/{chunk-H5DIRCZP.js.map → chunk-64U42JL5.js.map} +0 -0
  279. /package/dist/{chunk-4XINO77L.js.map → chunk-66JOLMWS.js.map} +0 -0
  280. /package/dist/{chunk-WHROVQ5Q.js.map → chunk-66PEACZP.js.map} +0 -0
  281. /package/dist/{chunk-GKL6QYZO.js.map → chunk-6L63IN3M.js.map} +0 -0
  282. /package/dist/{chunk-Y3XPSGNF.js.map → chunk-6QS5NQJH.js.map} +0 -0
  283. /package/dist/{chunk-M4IEZGCK.js.map → chunk-BFBS7OFQ.js.map} +0 -0
  284. /package/dist/{chunk-4PG5R6BJ.js.map → chunk-BNU5TQLR.js.map} +0 -0
  285. /package/dist/{chunk-B3MWELZ3.js.map → chunk-BW33YTF3.js.map} +0 -0
  286. /package/dist/{chunk-H6VJSITJ.js.map → chunk-CIL4GHMD.js.map} +0 -0
  287. /package/dist/{chunk-5UM4H64E.js.map → chunk-D3AGBN6G.js.map} +0 -0
  288. /package/dist/{chunk-IG3EZ3TF.js.map → chunk-DM3UZUCN.js.map} +0 -0
  289. /package/dist/{chunk-PMDOGXA2.js.map → chunk-EF3HNVL4.js.map} +0 -0
  290. /package/dist/{chunk-TVE7W67N.js.map → chunk-EIZ4MK33.js.map} +0 -0
  291. /package/dist/{chunk-AUFPPBC3.js.map → chunk-ENPJX3BB.js.map} +0 -0
  292. /package/dist/{chunk-63A2BU2B.js.map → chunk-F4ZHKDBW.js.map} +0 -0
  293. /package/dist/{chunk-3LBDEGQA.js.map → chunk-FDUCIUQ5.js.map} +0 -0
  294. /package/dist/{chunk-CTH2CQCB.js.map → chunk-FM6UKZAE.js.map} +0 -0
  295. /package/dist/{chunk-XVZA6VXC.js.map → chunk-FZVROZKC.js.map} +0 -0
  296. /package/dist/{chunk-IAGVM7EY.js.map → chunk-GMSRLDVR.js.map} +0 -0
  297. /package/dist/{chunk-HPB5TF55.js.map → chunk-GT4G2S2Y.js.map} +0 -0
  298. /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
  299. /package/dist/{chunk-5MGKGEL7.js.map → chunk-HNF7IMYQ.js.map} +0 -0
  300. /package/dist/{chunk-W7K5BP3M.js.map → chunk-HQPFACXO.js.map} +0 -0
  301. /package/dist/{chunk-TECK5IVY.js.map → chunk-HZNURRDS.js.map} +0 -0
  302. /package/dist/{chunk-SXTVZ6JL.js.map → chunk-I6HRHJRA.js.map} +0 -0
  303. /package/dist/{chunk-NHWDTOBN.js.map → chunk-ILD7N3XC.js.map} +0 -0
  304. /package/dist/{chunk-4NV7O76Q.js.map → chunk-J5TVHCDG.js.map} +0 -0
  305. /package/dist/{chunk-AL3TCNSJ.js.map → chunk-J6GZ2254.js.map} +0 -0
  306. /package/dist/{chunk-OH55F76C.js.map → chunk-JEX2JL3U.js.map} +0 -0
  307. /package/dist/{chunk-AVGDYYMA.js.map → chunk-JGQEF33K.js.map} +0 -0
  308. /package/dist/{chunk-6P7U3HWG.js.map → chunk-K36RMINQ.js.map} +0 -0
  309. /package/dist/{chunk-UW6LBFYN.js.map → chunk-K47G2CBP.js.map} +0 -0
  310. /package/dist/{chunk-3RHU5MD7.js.map → chunk-K7LLGCPH.js.map} +0 -0
  311. /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
  312. /package/dist/{chunk-R2BIVU2Z.js.map → chunk-LTST4R7U.js.map} +0 -0
  313. /package/dist/{chunk-IZBIDPMY.js.map → chunk-LYTKNK5O.js.map} +0 -0
  314. /package/dist/{chunk-HH73M65H.js.map → chunk-MCFI6G7U.js.map} +0 -0
  315. /package/dist/{chunk-V6SBA5O7.js.map → chunk-MFKBYXBY.js.map} +0 -0
  316. /package/dist/{chunk-AYJQ644F.js.map → chunk-MK7HXGM6.js.map} +0 -0
  317. /package/dist/{chunk-MOIJWWNA.js.map → chunk-MKMIY3E7.js.map} +0 -0
  318. /package/dist/{chunk-EXQJMFRF.js.map → chunk-MRN2MKB7.js.map} +0 -0
  319. /package/dist/{chunk-W26D2USW.js.map → chunk-MUH7RR6T.js.map} +0 -0
  320. /package/dist/{chunk-64JLBFCQ.js.map → chunk-N7NAGECH.js.map} +0 -0
  321. /package/dist/{chunk-75DCOS6I.js.map → chunk-OAKOAOEG.js.map} +0 -0
  322. /package/dist/{chunk-DDBB55L2.js.map → chunk-OAQO5AZJ.js.map} +0 -0
  323. /package/dist/{chunk-OPFVXM2D.js.map → chunk-OX6GY22Q.js.map} +0 -0
  324. /package/dist/{chunk-XWE3EOWX.js.map → chunk-RZED7FAU.js.map} +0 -0
  325. /package/dist/{chunk-YSATDEBY.js.map → chunk-SBRUTD5O.js.map} +0 -0
  326. /package/dist/{chunk-ZORS5BG5.js.map → chunk-SQO5SEKA.js.map} +0 -0
  327. /package/dist/{chunk-PRKNAIGZ.js.map → chunk-T6T2RMWW.js.map} +0 -0
  328. /package/dist/{chunk-O5ZINWW3.js.map → chunk-THFSCUFC.js.map} +0 -0
  329. /package/dist/{chunk-CIEEYVUB.js.map → chunk-TPX7XXY4.js.map} +0 -0
  330. /package/dist/{chunk-OCK4D5PC.js.map → chunk-TSZP7GGJ.js.map} +0 -0
  331. /package/dist/{chunk-L334PSLW.js.map → chunk-U2A56LLF.js.map} +0 -0
  332. /package/dist/{chunk-TGQW3AMK.js.map → chunk-UIFWZMO7.js.map} +0 -0
  333. /package/dist/{chunk-LN4X2NDA.js.map → chunk-UJ2FP2RV.js.map} +0 -0
  334. /package/dist/{chunk-4MPAKYZU.js.map → chunk-VDYRXI3E.js.map} +0 -0
  335. /package/dist/{chunk-IHCXMYSE.js.map → chunk-VPEIWMUT.js.map} +0 -0
  336. /package/dist/{chunk-ELHJK7LM.js.map → chunk-WBN66HPI.js.map} +0 -0
  337. /package/dist/{chunk-BJLCV2P3.js.map → chunk-WGRAB3KB.js.map} +0 -0
  338. /package/dist/{chunk-IVUVQPTQ.js.map → chunk-WIW4LOZL.js.map} +0 -0
  339. /package/dist/{chunk-5KDDAQ6K.js.map → chunk-WN2DASTS.js.map} +0 -0
  340. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  341. /package/dist/{chunk-5Y6VA6X2.js.map → chunk-XDUP6SWG.js.map} +0 -0
  342. /package/dist/{chunk-GHHQLSSY.js.map → chunk-XR4QR3JT.js.map} +0 -0
  343. /package/dist/{chunk-64S6X4IU.js.map → chunk-XTN54NML.js.map} +0 -0
  344. /package/dist/{chunk-QGFR73SN.js.map → chunk-YIG4HPY4.js.map} +0 -0
  345. /package/dist/{chunk-UYUFDTIX.js.map → chunk-YIXWY4OZ.js.map} +0 -0
  346. /package/dist/{chunk-GJ4HU6LK.js.map → chunk-YLMTFRZR.js.map} +0 -0
  347. /package/dist/{chunk-GGD2YDS6.js.map → chunk-YRMSHA7H.js.map} +0 -0
  348. /package/dist/{chunk-KZZ5EZ7O.js.map → chunk-ZS5LREUZ.js.map} +0 -0
  349. /package/dist/{chunk-JLGBJ4AX.js.map → chunk-ZYEWTVNQ.js.map} +0 -0
package/src/lcm/engine.ts CHANGED
@@ -96,6 +96,17 @@ const EMPTY_LCM_STATS = {
96
96
  maxDepth: -1,
97
97
  } as const;
98
98
 
99
+ /**
100
+ * Upper bound on leaf batches summarized by one background job. Keeps a single
101
+ * job's archive read and wall time bounded; leftover batches stay durable and
102
+ * the job re-queues itself.
103
+ */
104
+ const LCM_SUMMARIZE_MAX_LEAF_BATCHES_PER_JOB = 16;
105
+
106
+ /** Retry pacing for a failed summary pass: base delay and backoff ceiling. */
107
+ const LCM_SUMMARIZE_RETRY_BASE_MS = 500;
108
+ const LCM_SUMMARIZE_RETRY_MAX_MS = 30_000;
109
+
99
110
  export class LcmEngine {
100
111
  private db: Database.Database | null = null;
101
112
  private archive: LcmArchive | null = null;
@@ -106,6 +117,17 @@ export class LcmEngine {
106
117
  private readonly config: LcmEngineConfig;
107
118
  private readonly memoryDir: string;
108
119
  private initPromise: Promise<void> | null = null;
120
+ private readonly summarizeRetryTimers = new Map<string, NodeJS.Timeout>();
121
+ private readonly summarizeRetryAttempts = new Map<string, number>();
122
+ /** Resolved when the session's pending retry timer has fired or been cleared. */
123
+ private readonly summarizeRetryWaiters = new Map<
124
+ string,
125
+ { promise: Promise<void>; resolve: () => void }
126
+ >();
127
+ /** Explicit idle waiters per session; referenced only while nonzero. */
128
+ private readonly summarizeRetryIdleWaiters = new Map<string, number>();
129
+ /** Base backoff delay; tests shrink it to keep the retry loop fast. */
130
+ private summarizeRetryBaseMs = LCM_SUMMARIZE_RETRY_BASE_MS;
109
131
  private readonly pendingObserveInitCounts = new Map<string, number>();
110
132
  private readonly pendingObserveInitWaiters = new Map<string, Array<() => void>>();
111
133
  private readonly pendingObserveInitIdleWaiters: Array<() => void> = [];
@@ -158,8 +180,8 @@ export class LcmEngine {
158
180
  );
159
181
  const observeQueue = new LcmWorkQueue({
160
182
  concurrency: this.config.observeConcurrency,
161
- worker: async (sessionId, messages) => {
162
- await this.processObserveMessages(sessionId, messages);
183
+ worker: async (sessionId) => {
184
+ await this.processSummarizeJob(sessionId);
163
185
  },
164
186
  hooks: {
165
187
  onJobStart: ({ sessionId, depth, inFlight, waitMs }) => {
@@ -200,12 +222,31 @@ export class LcmEngine {
200
222
  this.dag = dag;
201
223
  this.summarizer = summarizer;
202
224
  this.observeQueue = observeQueue;
225
+ this.resumeOutstandingSummaries();
203
226
  log.info("LCM engine initialized");
204
227
  }
205
228
 
206
229
  /**
207
- * Enqueue messages from agent_end hook.
208
- * The queue worker performs the archive append and incremental summarization.
230
+ * Restart resume: sessions whose durable archive still holds complete
231
+ * unsummarized batches get a summary job without any new observe call.
232
+ * Only session references are queued — the content already lives in SQLite.
233
+ */
234
+ private resumeOutstandingSummaries(): void {
235
+ if (!this.archive || !this.observeQueue) return;
236
+ const sessions = this.archive.listSessionsWithUnsummarizedTurns(
237
+ this.config.leafBatchSize,
238
+ );
239
+ if (sessions.length === 0) return;
240
+ for (const sessionId of sessions) this.observeQueue.enqueue(sessionId);
241
+ log.info(`LCM resume scheduled summary jobs: sessions=${sessions.length}`);
242
+ }
243
+
244
+ /**
245
+ * Archive observed messages and schedule background summarization.
246
+ *
247
+ * The archive append completes BEFORE this resolves: an accepted observe is
248
+ * already durable in SQLite, so a crash cannot lose it. Only the
249
+ * summarization is background work, and it resumes from the durable archive.
209
250
  */
210
251
  async observeMessages(
211
252
  sessionId: string,
@@ -213,53 +254,60 @@ export class LcmEngine {
213
254
  ): Promise<void> {
214
255
  const normalizedSessionId = normalizeLcmSessionId(sessionId);
215
256
  if (!normalizedSessionId) return;
216
- this.enqueueObserveMessages(normalizedSessionId, messages);
257
+ await this.enqueueObserveMessages(normalizedSessionId, messages);
217
258
  }
218
259
 
219
- /** Enqueue an observe job without waiting for worker completion. */
220
- enqueueObserveMessages(
260
+ /**
261
+ * Durably archive messages, then queue a metadata-only summary job.
262
+ *
263
+ * Rejects when the archive append fails — callers must not treat a failed
264
+ * archive as an accepted observation.
265
+ */
266
+ async enqueueObserveMessages(
221
267
  sessionId: string,
222
268
  messages: LcmObserveMessage[],
223
- ): void {
224
- if (!this.config.enabled || this.closed) return;
269
+ ): Promise<void> {
270
+ // LCM off is a feature gate (the caller reports "not archived"); a closed
271
+ // engine is a failure — accepting the turn and archiving nothing would
272
+ // acknowledge a turn the archive never stored.
273
+ if (!this.config.enabled) return;
274
+ if (this.closed) throw new Error("LCM engine is closed");
225
275
  const normalizedSessionId = normalizeLcmSessionId(sessionId);
226
276
  if (!normalizedSessionId) return;
227
277
  if (messages.length === 0) return;
228
278
 
229
279
  this.reservePendingObserveInit(normalizedSessionId);
230
280
 
231
- void this.ensureInitialized()
232
- .then(() => {
233
- if (this.closed || !this.observeQueue) return;
234
- this.observeQueue.enqueue(normalizedSessionId, messages);
235
- log.debug(
236
- `LCM observe enqueued: session=${normalizedSessionId}, depth=${this.observeQueue.depth}, inFlight=${this.observeQueue.inFlightCount}`,
237
- );
238
- })
239
- .catch((err) => {
240
- if (this.closed) return;
241
- log.error(
242
- `LCM observe enqueue initialization error: ${err}${nativeBindingRecoveryHint(err)}`,
243
- );
244
- })
245
- .finally(() => {
246
- this.releasePendingObserveInit(normalizedSessionId);
247
- });
281
+ try {
282
+ await this.ensureInitialized();
283
+ if (this.closed) throw new Error("LCM engine is closed");
284
+ if (!this.archive || !this.observeQueue) {
285
+ throw new Error("LCM engine is not initialized");
286
+ }
287
+
288
+ const archived = this.archiveObservedMessages(normalizedSessionId, messages);
289
+ this.observeQueue.enqueue(normalizedSessionId);
290
+ log.debug(
291
+ `LCM observe archived: session=${normalizedSessionId}, messages=${archived}, depth=${this.observeQueue.depth}, inFlight=${this.observeQueue.inFlightCount}`,
292
+ );
293
+ } catch (err) {
294
+ log.error(
295
+ `LCM observe archive failed: session=${normalizedSessionId}, error=${err}${nativeBindingRecoveryHint(err)}`,
296
+ );
297
+ throw err;
298
+ } finally {
299
+ this.releasePendingObserveInit(normalizedSessionId);
300
+ }
248
301
  }
249
302
 
250
- private async processObserveMessages(
303
+ /** Append observed messages to the archive; returns the number of rows. */
304
+ private archiveObservedMessages(
251
305
  sessionId: string,
252
306
  messages: LcmObserveMessage[],
253
- ): Promise<void> {
254
- if (this.closed) return;
255
- await this.ensureInitialized();
256
- if (this.closed || !this.archive || !this.summarizer) return;
257
-
258
- if (messages.length === 0) return;
259
-
260
- const currentMax = this.archive.getMaxTurnIndex(sessionId);
307
+ ): number {
308
+ const currentMax = this.archive!.getMaxTurnIndex(sessionId);
261
309
  const { messagePartsEnabled } = this.config;
262
- const newMessages = messages.map((m, i) => ({
310
+ const rows = messages.map((m, i) => ({
263
311
  turnIndex: currentMax + 1 + i,
264
312
  role: m.role,
265
313
  content: m.content,
@@ -268,16 +316,109 @@ export class LcmEngine {
268
316
  sourceFormat: messagePartsEnabled ? m.sourceFormat : undefined,
269
317
  }));
270
318
 
271
- this.archive.appendMessages(sessionId, newMessages, {
272
- messagePartsEnabled,
273
- });
319
+ this.archive!.appendMessages(sessionId, rows, { messagePartsEnabled });
320
+ return rows.length;
321
+ }
322
+
323
+ /**
324
+ * Summarize one session's durable backlog.
325
+ *
326
+ * The pass is capped so a long backlog never holds a job open indefinitely;
327
+ * remaining batches stay durable and the job re-queues itself.
328
+ */
329
+ private async processSummarizeJob(sessionId: string): Promise<void> {
330
+ if (this.closed) return;
331
+ await this.ensureInitialized();
332
+ if (this.closed || !this.archive || !this.summarizer || !this.observeQueue)
333
+ return;
274
334
 
275
- // Trigger incremental summarization inside the worker, after append.
276
335
  try {
277
- await this.summarizer.summarizeIncremental(sessionId);
336
+ await this.summarizer.summarizeIncremental(sessionId, {
337
+ maxLeafBatches: LCM_SUMMARIZE_MAX_LEAF_BATCHES_PER_JOB,
338
+ });
278
339
  } catch (err) {
279
- log.debug(`LCM incremental summarization error: ${err}`);
340
+ // The archived turns are durable; a failed pass must not strand them
341
+ // until the next observe or a restart.
342
+ log.error(`LCM incremental summarization failed: session=${sessionId}, error=${err}`);
343
+ this.scheduleSummarizeRetry(sessionId);
344
+ return;
345
+ }
346
+
347
+ this.clearSummarizeRetry(sessionId);
348
+
349
+ if (this.hasCompleteBatchPending(sessionId)) {
350
+ this.observeQueue.enqueue(sessionId);
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Schedule a metadata-only retry for a failed summary pass.
356
+ *
357
+ * The retry keeps trying for as long as the engine is open and complete
358
+ * batches remain — a transient failure must never strand durable work.
359
+ * What is bounded is the RATE, not the ability to retry: one pending timer
360
+ * per session, exponential backoff capped at
361
+ * {@link LCM_SUMMARIZE_RETRY_MAX_MS}, and an unreferenced timer that is
362
+ * cleared when the engine closes.
363
+ */
364
+ private scheduleSummarizeRetry(sessionId: string): void {
365
+ if (this.closed || this.summarizeRetryTimers.has(sessionId)) return;
366
+ const attempt = (this.summarizeRetryAttempts.get(sessionId) ?? 0) + 1;
367
+ this.summarizeRetryAttempts.set(sessionId, attempt);
368
+ const delayMs = Math.min(
369
+ LCM_SUMMARIZE_RETRY_MAX_MS,
370
+ this.summarizeRetryBaseMs * 2 ** Math.min(attempt - 1, 16),
371
+ );
372
+ // The waiter makes the pending retry visible to idle waits, so a direct
373
+ // flush (pre-compaction) serializes with the retry instead of racing it.
374
+ const retry = Promise.withResolvers<void>();
375
+ this.summarizeRetryWaiters.set(sessionId, retry);
376
+ const timer = setTimeout(() => {
377
+ this.summarizeRetryTimers.delete(sessionId);
378
+ this.summarizeRetryWaiters.delete(sessionId);
379
+ if (this.closed) {
380
+ retry.resolve();
381
+ return;
382
+ }
383
+ if (!this.hasCompleteBatchPending(sessionId)) {
384
+ this.summarizeRetryAttempts.delete(sessionId);
385
+ retry.resolve();
386
+ return;
387
+ }
388
+ // Re-queue before releasing the waiter: an idle wait released by the
389
+ // retry must observe the queued job, never run beside it.
390
+ this.observeQueue?.enqueue(sessionId);
391
+ retry.resolve();
392
+ }, delayMs);
393
+ // Pure background: unreferenced, so a one-shot host is never kept alive
394
+ // by a retry loop. An explicit idle waiter (see waitForSessionObserveIdle)
395
+ // references it for the duration of the wait, and close() clears all.
396
+ timer.unref?.();
397
+ this.summarizeRetryTimers.set(sessionId, timer);
398
+ }
399
+
400
+ /** True when the session still holds a complete unsummarized batch. */
401
+ private hasCompleteBatchPending(sessionId: string): boolean {
402
+ if (!this.archive) return false;
403
+ return (
404
+ this.archive.countUnsummarizedMessages(sessionId) >=
405
+ this.config.leafBatchSize
406
+ );
407
+ }
408
+
409
+ /** Drop a pending retry, its attempt counter, and release idle waiters. */
410
+ private clearSummarizeRetry(sessionId: string): void {
411
+ const timer = this.summarizeRetryTimers.get(sessionId);
412
+ if (timer) {
413
+ clearTimeout(timer);
414
+ this.summarizeRetryTimers.delete(sessionId);
280
415
  }
416
+ const waiter = this.summarizeRetryWaiters.get(sessionId);
417
+ if (waiter) {
418
+ this.summarizeRetryWaiters.delete(sessionId);
419
+ waiter.resolve();
420
+ }
421
+ this.summarizeRetryAttempts.delete(sessionId);
281
422
  }
282
423
 
283
424
  get observeQueueDepth(): number {
@@ -288,12 +429,22 @@ export class LcmEngine {
288
429
  return this.observeQueue?.inFlightCount ?? 0;
289
430
  }
290
431
 
291
- async waitForObserveQueueIdle(): Promise<void> {
432
+ async waitForObserveQueueIdle(abortSignal?: AbortSignal): Promise<void> {
433
+ abortSignal?.throwIfAborted();
292
434
  if (!this.config.enabled || this.closed) return;
293
435
  await this.ensureInitialized();
436
+ abortSignal?.throwIfAborted();
294
437
  if (this.closed) return;
295
- await this.waitForPendingObserveInitIdle();
296
- await this.observeQueue?.whenIdle();
438
+ for (;;) {
439
+ abortSignal?.throwIfAborted();
440
+ await this.waitForPendingObserveInitIdle(undefined, abortSignal);
441
+ await this.observeQueue?.whenIdle(abortSignal);
442
+ abortSignal?.throwIfAborted();
443
+ const entry = [...this.summarizeRetryWaiters.entries()][0];
444
+ if (!entry) return;
445
+ await this.awaitSummarizeRetry(entry[0], entry[1], abortSignal);
446
+ // A fired retry may have re-queued a session: wait for the queue again.
447
+ }
297
448
  }
298
449
 
299
450
  async waitForSessionObserveIdle(sessionId: string, abortSignal?: AbortSignal): Promise<void> {
@@ -304,8 +455,42 @@ export class LcmEngine {
304
455
  await this.ensureInitialized();
305
456
  abortSignal?.throwIfAborted();
306
457
  if (this.closed) return;
307
- await this.waitForPendingObserveInitIdle(normalizedSessionId, abortSignal);
308
- await this.observeQueue?.whenSessionIdle(normalizedSessionId, abortSignal) ?? Promise.resolve();
458
+ // Serialize with pending summary retries: a direct flush (pre-compaction)
459
+ // must not run beside a retry pass for the same session.
460
+ for (;;) {
461
+ await this.waitForPendingObserveInitIdle(normalizedSessionId, abortSignal);
462
+ await this.observeQueue?.whenSessionIdle(normalizedSessionId, abortSignal) ?? Promise.resolve();
463
+ const waiter = this.summarizeRetryWaiters.get(normalizedSessionId);
464
+ if (!waiter) return;
465
+ await this.awaitSummarizeRetry(normalizedSessionId, waiter, abortSignal);
466
+ // The retry fired and may have re-queued the session: wait again.
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Park an explicit idle wait on a pending retry. While any wait is parked,
472
+ * the retry timer is referenced so a one-shot host cannot exit under the
473
+ * wait; with no waiters the timer stays unreferenced background work.
474
+ */
475
+ private async awaitSummarizeRetry(
476
+ sessionId: string,
477
+ waiter: { promise: Promise<void>; resolve: () => void },
478
+ abortSignal?: AbortSignal,
479
+ ): Promise<void> {
480
+ const waiters = (this.summarizeRetryIdleWaiters.get(sessionId) ?? 0) + 1;
481
+ this.summarizeRetryIdleWaiters.set(sessionId, waiters);
482
+ this.summarizeRetryTimers.get(sessionId)?.ref?.();
483
+ try {
484
+ await abortOnSignal(abortSignal, waiter.promise);
485
+ } finally {
486
+ const remaining = (this.summarizeRetryIdleWaiters.get(sessionId) ?? 1) - 1;
487
+ if (remaining <= 0) {
488
+ this.summarizeRetryIdleWaiters.delete(sessionId);
489
+ this.summarizeRetryTimers.get(sessionId)?.unref?.();
490
+ } else {
491
+ this.summarizeRetryIdleWaiters.set(sessionId, remaining);
492
+ }
493
+ }
309
494
  }
310
495
 
311
496
  private reservePendingObserveInit(sessionId: string): void {
@@ -359,8 +544,18 @@ export class LcmEngine {
359
544
  }
360
545
 
361
546
  if (this.pendingObserveInitCounts.size === 0) return;
362
- await new Promise<void>((resolve) => {
363
- this.pendingObserveInitIdleWaiters.push(resolve);
547
+ await new Promise<void>((resolve, reject) => {
548
+ const finish = (): void => {
549
+ abortSignal?.removeEventListener("abort", onAbort);
550
+ resolve();
551
+ };
552
+ const onAbort = (): void => {
553
+ const remaining = this.pendingObserveInitIdleWaiters.filter((waiter) => waiter !== finish);
554
+ this.pendingObserveInitIdleWaiters.splice(0, this.pendingObserveInitIdleWaiters.length, ...remaining);
555
+ reject(abortSignal?.reason instanceof Error ? abortSignal.reason : new DOMException("Aborted", "AbortError"));
556
+ };
557
+ this.pendingObserveInitIdleWaiters.push(finish);
558
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
364
559
  });
365
560
  }
366
561
 
@@ -697,23 +892,25 @@ export class LcmEngine {
697
892
  }
698
893
 
699
894
  /** Clear all LCM archive and summary state for one session. */
700
- async clearSession(sessionId: string): Promise<void> {
895
+ async clearSession(sessionId: string, abortSignal?: AbortSignal): Promise<void> {
701
896
  if (!this.config.enabled || this.closed) return;
702
897
  const normalizedSessionId = normalizeLcmSessionId(sessionId);
703
898
  if (!normalizedSessionId) return;
704
899
  await this.ensureInitialized();
705
900
  if (this.closed || !this.archive || !this.dag) return;
706
- await this.waitForSessionObserveIdle(normalizedSessionId);
901
+ await this.waitForSessionObserveIdle(normalizedSessionId, abortSignal);
902
+ abortSignal?.throwIfAborted();
707
903
  this.dag.deleteSession(normalizedSessionId);
708
904
  this.archive.deleteSession(normalizedSessionId);
709
905
  }
710
906
 
711
907
  /** Clear all LCM archive and summary state without closing the engine. */
712
- async clearAll(): Promise<void> {
908
+ async clearAll(abortSignal?: AbortSignal): Promise<void> {
713
909
  if (!this.config.enabled || this.closed) return;
714
910
  await this.ensureInitialized();
715
911
  if (this.closed || !this.archive || !this.dag) return;
716
- await this.waitForObserveQueueIdle();
912
+ await this.waitForObserveQueueIdle(abortSignal);
913
+ abortSignal?.throwIfAborted();
717
914
  this.dag.deleteAll();
718
915
  this.archive.deleteAll();
719
916
  }
@@ -735,6 +932,12 @@ export class LcmEngine {
735
932
  /** Close the database connection. */
736
933
  close(): void {
737
934
  this.closed = true;
935
+ for (const timer of this.summarizeRetryTimers.values()) clearTimeout(timer);
936
+ this.summarizeRetryTimers.clear();
937
+ this.summarizeRetryAttempts.clear();
938
+ for (const retry of this.summarizeRetryWaiters.values()) retry.resolve();
939
+ this.summarizeRetryWaiters.clear();
940
+ this.summarizeRetryIdleWaiters.clear();
738
941
  if (this.db) {
739
942
  this.db.close();
740
943
  }
@@ -26,9 +26,9 @@ test("LcmWorkQueue coalesces duplicate pending jobs per session", async () => {
26
26
  },
27
27
  });
28
28
 
29
- queue.enqueue("blocked", [{ role: "user", content: "hold" }]);
30
- queue.enqueue("session-a", [{ role: "user", content: "one" }]);
31
- queue.enqueue("session-a", [{ role: "assistant", content: "two" }]);
29
+ queue.enqueue("blocked");
30
+ queue.enqueue("session-a");
31
+ queue.enqueue("session-a");
32
32
 
33
33
  await Promise.resolve();
34
34
 
@@ -56,8 +56,8 @@ test("LcmWorkQueue continues after a worker failure", async () => {
56
56
  },
57
57
  });
58
58
 
59
- queue.enqueue("session-a", [{ role: "user", content: "one" }]);
60
- queue.enqueue("session-b", [{ role: "assistant", content: "two" }]);
59
+ queue.enqueue("session-a");
60
+ queue.enqueue("session-b");
61
61
 
62
62
  await queue.whenIdle();
63
63
 
@@ -87,14 +87,14 @@ test("LcmWorkQueue preserves first enqueue time when pending jobs coalesce", asy
87
87
  },
88
88
  });
89
89
 
90
- queue.enqueue("blocked", [{ role: "user", content: "hold" }]);
90
+ queue.enqueue("blocked");
91
91
  await Promise.resolve();
92
92
 
93
93
  now = 110;
94
- queue.enqueue("session-a", [{ role: "user", content: "one" }]);
94
+ queue.enqueue("session-a");
95
95
 
96
96
  now = 150;
97
- queue.enqueue("session-a", [{ role: "assistant", content: "two" }]);
97
+ queue.enqueue("session-a");
98
98
 
99
99
  now = 200;
100
100
  blocker.resolve();
@@ -127,10 +127,10 @@ test("LcmWorkQueue does not start a second job for a session already in flight",
127
127
  },
128
128
  });
129
129
 
130
- queue.enqueue("session-a", [{ role: "user", content: "first" }]);
130
+ queue.enqueue("session-a");
131
131
  await Promise.resolve();
132
- queue.enqueue("session-a", [{ role: "assistant", content: "second" }]);
133
- queue.enqueue("session-b", [{ role: "user", content: "other" }]);
132
+ queue.enqueue("session-a");
133
+ queue.enqueue("session-b");
134
134
 
135
135
  await Promise.resolve();
136
136
  await Promise.resolve();
@@ -159,8 +159,8 @@ test("LcmWorkQueue waits for a specific session without waiting for unrelated id
159
159
  },
160
160
  });
161
161
 
162
- queue.enqueue("blocked", [{ role: "user", content: "hold" }]);
163
- queue.enqueue("session-a", [{ role: "assistant", content: "queued" }]);
162
+ queue.enqueue("blocked");
163
+ queue.enqueue("session-a");
164
164
 
165
165
  await Promise.resolve();
166
166
  const sessionIdlePromise = queue.whenSessionIdle("session-a").then(() => {
@@ -185,7 +185,7 @@ test("#3077 whenSessionIdle abort unregisters the waiter", async () => {
185
185
  await blocker.promise;
186
186
  },
187
187
  });
188
- queue.enqueue("blocked", [{ role: "user", content: "hold" }]);
188
+ queue.enqueue("blocked");
189
189
  await Promise.resolve();
190
190
  const abort = new AbortController();
191
191
  const waiting = queue.whenSessionIdle("blocked", abort.signal);
@@ -195,3 +195,21 @@ test("#3077 whenSessionIdle abort unregisters the waiter", async () => {
195
195
  await queue.whenIdle();
196
196
  await queue.whenSessionIdle("blocked");
197
197
  });
198
+
199
+ test("whenIdle abort unregisters the waiter while a job is in flight", async () => {
200
+ const blocker = deferred();
201
+ const queue = new LcmWorkQueue({
202
+ concurrency: 1,
203
+ worker: async () => {
204
+ await blocker.promise;
205
+ },
206
+ });
207
+ queue.enqueue("blocked");
208
+ await Promise.resolve();
209
+ const abort = new AbortController();
210
+ const waiting = queue.whenIdle(abort.signal);
211
+ abort.abort();
212
+ await assert.rejects(waiting, { name: "AbortError" });
213
+ blocker.resolve();
214
+ await queue.whenIdle();
215
+ });
package/src/lcm/queue.ts CHANGED
@@ -26,13 +26,12 @@ export interface LcmWorkQueueHooks {
26
26
 
27
27
  export interface LcmWorkQueueOptions {
28
28
  concurrency?: number;
29
- worker: (sessionId: string, messages: LcmObserveMessage[]) => Promise<void>;
29
+ worker: (sessionId: string) => Promise<void>;
30
30
  hooks?: LcmWorkQueueHooks;
31
31
  }
32
32
 
33
33
  interface PendingJob {
34
34
  sessionId: string;
35
- messages: LcmObserveMessage[];
36
35
  enqueuedAt: number;
37
36
  }
38
37
 
@@ -42,13 +41,14 @@ interface PendingJob {
42
41
  * Jobs are coalesced per session while they are pending. A session can have
43
42
  * at most one in-flight job at a time, and the queue processes jobs FIFO up to
44
43
  * the configured concurrency limit.
44
+ *
45
+ * The queue carries session references only — never message payloads. Observe
46
+ * content is archived in SQLite before it is queued, so a pending job is a
47
+ * "this session has durable unsummarized turns" marker.
45
48
  */
46
49
  export class LcmWorkQueue {
47
50
  private readonly concurrency: number;
48
- private readonly worker: (
49
- sessionId: string,
50
- messages: LcmObserveMessage[],
51
- ) => Promise<void>;
51
+ private readonly worker: (sessionId: string) => Promise<void>;
52
52
  private readonly hooks: LcmWorkQueueHooks;
53
53
  private readonly pending = new Map<string, PendingJob>();
54
54
  private readonly inFlightSessions = new Set<string>();
@@ -63,17 +63,14 @@ export class LcmWorkQueue {
63
63
  this.hooks = options.hooks ?? {};
64
64
  }
65
65
 
66
- enqueue(sessionId: string, messages: LcmObserveMessage[]): void {
67
- if (messages.length === 0) return;
66
+ enqueue(sessionId: string): void {
67
+ if (sessionId.length === 0) return;
68
68
 
69
69
  const now = Date.now();
70
70
  const existing = this.pending.get(sessionId);
71
- if (existing) {
72
- existing.messages.push(...messages.map((message) => ({ ...message })));
73
- } else {
71
+ if (!existing) {
74
72
  this.pending.set(sessionId, {
75
73
  sessionId,
76
- messages: messages.map((message) => ({ ...message })),
77
74
  enqueuedAt: now,
78
75
  });
79
76
  }
@@ -89,10 +86,21 @@ export class LcmWorkQueue {
89
86
  return this.inFlight;
90
87
  }
91
88
 
92
- async whenIdle(): Promise<void> {
89
+ async whenIdle(abortSignal?: AbortSignal): Promise<void> {
90
+ abortSignal?.throwIfAborted();
93
91
  if (this.depth === 0 && this.inFlight === 0) return;
94
- await new Promise<void>((resolve) => {
95
- this.idleWaiters.push(resolve);
92
+ await new Promise<void>((resolve, reject) => {
93
+ const finish = (): void => {
94
+ abortSignal?.removeEventListener("abort", onAbort);
95
+ resolve();
96
+ };
97
+ const onAbort = (): void => {
98
+ const remaining = this.idleWaiters.filter((waiter) => waiter !== finish);
99
+ this.idleWaiters.splice(0, this.idleWaiters.length, ...remaining);
100
+ reject(abortSignal?.reason instanceof Error ? abortSignal.reason : new DOMException("Aborted", "AbortError"));
101
+ };
102
+ this.idleWaiters.push(finish);
103
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
96
104
  });
97
105
  }
98
106
 
@@ -162,7 +170,7 @@ export class LcmWorkQueue {
162
170
  let error: unknown;
163
171
 
164
172
  try {
165
- await this.worker(job.sessionId, job.messages);
173
+ await this.worker(job.sessionId);
166
174
  } catch (err) {
167
175
  error = err;
168
176
  } finally {
@@ -33,12 +33,25 @@ export class LcmSummarizer {
33
33
  /**
34
34
  * Run incremental summarization for a session.
35
35
  * Creates leaf nodes from unsummarized messages, then rolls up as needed.
36
+ *
37
+ * `maxLeafBatches` bounds one pass: the archive read is limited to that
38
+ * many complete batches, so a long backlog never loads the whole
39
+ * unsummarized tail into memory. Remaining batches stay durable in the
40
+ * archive and are picked up by the next pass.
36
41
  */
37
- async summarizeIncremental(sessionId: string): Promise<number> {
42
+ async summarizeIncremental(
43
+ sessionId: string,
44
+ options: { maxLeafBatches?: number } = {},
45
+ ): Promise<number> {
38
46
  let nodesCreated = 0;
39
47
 
40
48
  // Phase 1: Create leaf nodes from unsummarized messages
41
- const unsummarized = this.archive.getUnsummarizedMessages(sessionId);
49
+ const maxLeafBatches = options.maxLeafBatches;
50
+ const readLimit =
51
+ maxLeafBatches === undefined
52
+ ? undefined
53
+ : maxLeafBatches * this.config.leafBatchSize;
54
+ const unsummarized = this.archive.getUnsummarizedMessages(sessionId, readLimit);
42
55
  if (unsummarized.length >= this.config.leafBatchSize) {
43
56
  const batches = chunkArray(unsummarized, this.config.leafBatchSize);
44
57
  // Only process complete batches (leave partial for next time)