@remnic/core 10.0.35 → 10.0.37

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 (385) hide show
  1. package/dist/access-admin-ops-surface.js +81 -81
  2. package/dist/access-authorization-probe.js +82 -82
  3. package/dist/access-boundary.js +81 -81
  4. package/dist/access-citation-namespace.js +2 -2
  5. package/dist/access-cli.js +172 -170
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +81 -81
  8. package/dist/access-http-briefing.js +82 -82
  9. package/dist/access-http-query.js +83 -83
  10. package/dist/access-http.js +104 -104
  11. package/dist/access-identity-continuity-surface.js +81 -81
  12. package/dist/access-lcm-surface.js +81 -81
  13. package/dist/access-mcp.js +93 -93
  14. package/dist/access-namespace-preflight.js +81 -81
  15. package/dist/access-observe-write-category.js +13 -13
  16. package/dist/access-observe-write-surface.js +81 -81
  17. package/dist/access-offline-manifest.js +12 -12
  18. package/dist/access-operations-batch.js +85 -85
  19. package/dist/access-operations.js +92 -92
  20. package/dist/access-recall-concurrency.js +81 -81
  21. package/dist/access-recall-response.js +81 -81
  22. package/dist/access-recall-surface.js +81 -81
  23. package/dist/access-schema.js +8 -8
  24. package/dist/access-service.js +81 -81
  25. package/dist/access-surface-catalog.js +83 -83
  26. package/dist/active-recall.js +40 -40
  27. package/dist/adapters/index.js +7 -7
  28. package/dist/adapters/registry.js +3 -3
  29. package/dist/{auto-sync-VPHR3FBI.js → auto-sync-R3XGJL5I.js} +7 -7
  30. package/dist/briefing.js +29 -29
  31. package/dist/buffer-eviction.d.ts +16 -0
  32. package/dist/buffer-eviction.d.ts.map +1 -0
  33. package/dist/buffer-eviction.js +8 -0
  34. package/dist/buffer-persistence.d.ts +45 -0
  35. package/dist/buffer-persistence.d.ts.map +1 -0
  36. package/dist/buffer-persistence.js +8 -0
  37. package/dist/buffer.d.ts +5 -2
  38. package/dist/buffer.d.ts.map +1 -1
  39. package/dist/buffer.js +13 -11
  40. package/dist/calibration.js +3 -3
  41. package/dist/causal-behavior.js +2 -2
  42. package/dist/causal-chain.js +2 -2
  43. package/dist/causal-consolidation.js +32 -32
  44. package/dist/causal-retrieval.js +2 -2
  45. package/dist/causal-trajectory-graph.js +2 -2
  46. package/dist/causal-trajectory.js +1 -1
  47. package/dist/{chunk-HT6IEMQ2.js → chunk-2RJO7VS4.js} +2 -2
  48. package/dist/{chunk-YLF42JQ7.js → chunk-3BPMYMPX.js} +2 -2
  49. package/dist/{chunk-GAARPWMN.js → chunk-3L5WH6CV.js} +2 -2
  50. package/dist/{chunk-XVZA6VXC.js → chunk-3SL52JKD.js} +4 -4
  51. package/dist/chunk-4OFXLQJD.js +58 -0
  52. package/dist/chunk-4OFXLQJD.js.map +1 -0
  53. package/dist/{chunk-S2PDFSXS.js → chunk-4P4VLPGB.js} +1 -1
  54. package/dist/{chunk-2ZYIAUYU.js → chunk-5CWH2LKL.js} +4 -4
  55. package/dist/{chunk-G2PC6IZB.js → chunk-5U3NUHMA.js} +86 -86
  56. package/dist/{chunk-4LOMATQA.js → chunk-5UWP5X3T.js} +2 -2
  57. package/dist/{chunk-JUXOTY6J.js → chunk-5YQXWVCP.js} +9 -2
  58. package/dist/chunk-5YQXWVCP.js.map +1 -0
  59. package/dist/{chunk-G3T6YMBO.js → chunk-64S6X4IU.js} +7 -7
  60. package/dist/{chunk-NHB4RC3G.js → chunk-6QOL4G76.js} +2 -2
  61. package/dist/{chunk-GRBQO56D.js → chunk-6R6CU3MR.js} +51 -51
  62. package/dist/{chunk-SQ554O3K.js → chunk-74AY3S7U.js} +3 -3
  63. package/dist/{chunk-4ISHIPOC.js → chunk-7PG7QBWD.js} +2 -2
  64. package/dist/{chunk-A4B7WL5N.js → chunk-7ZBANI3M.js} +2 -2
  65. package/dist/{chunk-DG5UKN2Y.js → chunk-A23DPFFI.js} +2 -2
  66. package/dist/{chunk-77ZK533I.js → chunk-ARFT6ZBE.js} +2 -2
  67. package/dist/{chunk-V44DWTFA.js → chunk-AYXRK264.js} +6 -6
  68. package/dist/{chunk-GRZX3RHY.js → chunk-BPBUUGAO.js} +2 -2
  69. package/dist/{chunk-DFP37GUN.js → chunk-CCYXCT4B.js} +2 -2
  70. package/dist/{chunk-JJWOKRZA.js → chunk-CNQQNYJ2.js} +2 -2
  71. package/dist/{chunk-QZP3RMYP.js → chunk-CU7WDDPV.js} +4 -4
  72. package/dist/{chunk-GY4R4P2A.js → chunk-CUWOSRCS.js} +2 -2
  73. package/dist/{chunk-CJKHLACF.js → chunk-D2AKUKAQ.js} +2 -2
  74. package/dist/{chunk-BRZRP5WD.js → chunk-DK5VMJL3.js} +2 -2
  75. package/dist/{chunk-77IXBNV6.js → chunk-E24RUQEN.js} +2 -2
  76. package/dist/{chunk-PTQZPVRQ.js → chunk-E5PVMB3I.js} +2 -2
  77. package/dist/{chunk-AHS3QEEA.js → chunk-EEOYF3IB.js} +2 -2
  78. package/dist/{chunk-PH33R7GN.js → chunk-EHIKY2IA.js} +2 -2
  79. package/dist/{chunk-NLQBTHBV.js → chunk-EVVCE7S2.js} +2 -2
  80. package/dist/{chunk-FQ52AJXX.js → chunk-FBAHBFT5.js} +2 -2
  81. package/dist/{chunk-D75ET36O.js → chunk-FL6SLVAJ.js} +2 -2
  82. package/dist/{chunk-6NAN4WFY.js → chunk-FO5AFCRI.js} +5 -5
  83. package/dist/{chunk-5LWODP5R.js → chunk-FRAL3EU4.js} +2 -2
  84. package/dist/{chunk-AAANTXCQ.js → chunk-FZVZYU5C.js} +2 -2
  85. package/dist/{chunk-GQVTALUJ.js → chunk-G2SRB62M.js} +2 -2
  86. package/dist/{chunk-ORWDCX5Q.js → chunk-G6PPA4TH.js} +11 -11
  87. package/dist/{chunk-VNAIUNXI.js → chunk-GCVUBVEN.js} +10 -10
  88. package/dist/{chunk-TGAXRT7N.js → chunk-GFYFKXWZ.js} +2 -2
  89. package/dist/{chunk-ZABEDRGQ.js → chunk-HIMXDRSQ.js} +2 -2
  90. package/dist/{chunk-GDYLZ6FR.js → chunk-HJ3UTGIW.js} +3 -3
  91. package/dist/{chunk-MGXLLL5L.js → chunk-HKEK27M4.js} +2 -2
  92. package/dist/{chunk-X3IFRQKS.js → chunk-I73ZSORJ.js} +4 -4
  93. package/dist/{chunk-XUB4I2N6.js → chunk-JFI3ATK4.js} +2 -2
  94. package/dist/{chunk-R4SATHCV.js → chunk-JH2ALMPR.js} +2 -2
  95. package/dist/{chunk-TKBW4YLS.js → chunk-JH4FCVWJ.js} +2 -2
  96. package/dist/{chunk-UAYA4NJN.js → chunk-JWTUJVIF.js} +28 -36
  97. package/dist/chunk-JWTUJVIF.js.map +1 -0
  98. package/dist/chunk-KASJJGRG.js +29 -0
  99. package/dist/chunk-KASJJGRG.js.map +1 -0
  100. package/dist/{chunk-X73SCHKR.js → chunk-KC5SVCUJ.js} +2 -2
  101. package/dist/{chunk-6NXHX6MI.js → chunk-KOY25HUB.js} +2 -2
  102. package/dist/{chunk-TRW7N4FS.js → chunk-L2JQSG5M.js} +2 -2
  103. package/dist/{chunk-SIIWHIBK.js → chunk-L46Y5FPE.js} +2 -2
  104. package/dist/{chunk-CIJRM6SQ.js → chunk-LAQS5HYU.js} +2 -2
  105. package/dist/{chunk-TYT4TEQV.js → chunk-LDWE7OZS.js} +6 -2
  106. package/dist/chunk-LDWE7OZS.js.map +1 -0
  107. package/dist/{chunk-23VGZ65V.js → chunk-LHBOFROT.js} +2 -2
  108. package/dist/{chunk-LO775P3S.js → chunk-LRJKHXU4.js} +2 -2
  109. package/dist/{chunk-QISYYK4L.js → chunk-LSSCMZQ2.js} +3 -3
  110. package/dist/{chunk-PCIIKQXW.js → chunk-MBMT5FUQ.js} +2 -2
  111. package/dist/{chunk-XFDHL252.js → chunk-MGXFJOUR.js} +3 -3
  112. package/dist/{chunk-7WTTE5FW.js → chunk-MH3LC7KB.js} +2 -2
  113. package/dist/{chunk-ERMT5WQM.js → chunk-MKE5DP3S.js} +2 -2
  114. package/dist/{chunk-DJMC4W6N.js → chunk-MNDHKZZQ.js} +2 -2
  115. package/dist/{chunk-7FG2JSU6.js → chunk-NMKETOAX.js} +2 -2
  116. package/dist/{chunk-TVM47JQB.js → chunk-NQOM6PHO.js} +6 -6
  117. package/dist/{chunk-AI2VXOSO.js → chunk-NSQSOLCE.js} +2 -2
  118. package/dist/{chunk-UR25IQVT.js → chunk-P5CUEB2Q.js} +8 -8
  119. package/dist/{chunk-FXO43NBW.js → chunk-PSQ2P2T3.js} +3 -3
  120. package/dist/{chunk-KVNOHMVE.js → chunk-PTRPOJ7U.js} +2 -2
  121. package/dist/{chunk-SXRX6K35.js → chunk-QEGE5QCL.js} +47 -47
  122. package/dist/{chunk-CM6JDFCN.js → chunk-QIC7YZ3D.js} +16 -16
  123. package/dist/{chunk-KRAMCBIW.js → chunk-QKXPOLQD.js} +4 -4
  124. package/dist/{chunk-O3VKQMWC.js → chunk-R6TCM47S.js} +364 -166
  125. package/dist/chunk-R6TCM47S.js.map +1 -0
  126. package/dist/{chunk-G5UR4DXQ.js → chunk-RBC4GIZU.js} +2 -2
  127. package/dist/{chunk-GK56GNNS.js → chunk-RVYXSS3Q.js} +8 -8
  128. package/dist/{chunk-G7XAZU2L.js → chunk-RWWYBE7E.js} +3 -3
  129. package/dist/{chunk-HJM23E65.js → chunk-S53WZWGL.js} +2 -2
  130. package/dist/{chunk-MLED254J.js → chunk-T6OZW2WR.js} +8 -2
  131. package/dist/chunk-T6OZW2WR.js.map +1 -0
  132. package/dist/{chunk-3ODEGTVV.js → chunk-TDCR6PQL.js} +2 -2
  133. package/dist/{chunk-QCP75JGO.js → chunk-TFA6HPOA.js} +5 -5
  134. package/dist/{chunk-SE3JAD22.js → chunk-TPKB7T6Z.js} +2 -2
  135. package/dist/{chunk-6VYY2XFK.js → chunk-TVIGN3XZ.js} +19 -19
  136. package/dist/{chunk-ZG3HZZ6K.js → chunk-TYGLV6ZG.js} +5 -1
  137. package/dist/{chunk-ZG3HZZ6K.js.map → chunk-TYGLV6ZG.js.map} +1 -1
  138. package/dist/{chunk-K567EIW3.js → chunk-U4JXQCJ3.js} +2 -2
  139. package/dist/{chunk-WI6MIJYK.js → chunk-U7HJBODU.js} +5 -5
  140. package/dist/{chunk-IS4HKNWP.js → chunk-UBN5AOLT.js} +2 -2
  141. package/dist/{chunk-7VJEZQYF.js → chunk-UFBIOF7C.js} +3 -3
  142. package/dist/{chunk-JNELS2TW.js → chunk-UJDTXWMT.js} +2 -2
  143. package/dist/{chunk-M3GKSCFN.js → chunk-ULUX6Q7R.js} +4 -4
  144. package/dist/{chunk-ZRU7636R.js → chunk-UMFBHYPW.js} +12 -12
  145. package/dist/{chunk-GKNX4QO3.js → chunk-UQWA5QDT.js} +3 -3
  146. package/dist/{chunk-4Z7O2QVJ.js → chunk-USOXPN5H.js} +2 -2
  147. package/dist/{chunk-XA32GZ6B.js → chunk-UXXVOA4D.js} +3 -3
  148. package/dist/{chunk-OLAQWYNF.js → chunk-V5RJCXLG.js} +6 -6
  149. package/dist/{chunk-A2D3WICT.js → chunk-VGFNTY3Y.js} +2 -2
  150. package/dist/{chunk-EH6KLYIE.js → chunk-VKTP3TZB.js} +4 -4
  151. package/dist/{chunk-CSGFGQBI.js → chunk-VPO53QMG.js} +2 -2
  152. package/dist/{chunk-P2RCFQA6.js → chunk-WADPRAZE.js} +2 -2
  153. package/dist/{chunk-IVYAPAHG.js → chunk-WRDFYSUC.js} +2 -2
  154. package/dist/{chunk-FW7KWZBI.js → chunk-WSLNC5AL.js} +8 -8
  155. package/dist/{chunk-C6S2Y7HE.js → chunk-XKDQTDXN.js} +4 -4
  156. package/dist/{chunk-4AHKNZW7.js → chunk-XLEP7SS3.js} +2 -2
  157. package/dist/{chunk-B6L7BKA7.js → chunk-XRFLR3XF.js} +4 -4
  158. package/dist/{chunk-H7QC7WCU.js → chunk-XTBEXZFN.js} +4 -4
  159. package/dist/{chunk-S2S4U4G3.js → chunk-Y6Y3TRBN.js} +4 -4
  160. package/dist/{chunk-H6SKBCNI.js → chunk-YC2C67F7.js} +2 -2
  161. package/dist/{chunk-6FIFEXFF.js → chunk-YGYT7S3G.js} +7 -7
  162. package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
  163. package/dist/{chunk-YOL56HY5.js → chunk-YQ6SLFNZ.js} +3 -3
  164. package/dist/{chunk-G22A6WJQ.js → chunk-Z2T5BPOZ.js} +2 -2
  165. package/dist/{chunk-IHTKBQBS.js → chunk-Z5AQZRHF.js} +2 -2
  166. package/dist/{chunk-QQ4T622E.js → chunk-Z5QWUR3Y.js} +2 -2
  167. package/dist/cli.js +131 -131
  168. package/dist/coding/export-okf-codegraph.js +30 -30
  169. package/dist/coding/pre-action-gate.js +2 -2
  170. package/dist/compounding/engine.js +31 -31
  171. package/dist/config.js +40 -40
  172. package/dist/connectors/codex-materialize-runner.js +29 -29
  173. package/dist/connectors/index.js +29 -29
  174. package/dist/contradiction/index.js +5 -5
  175. package/dist/convergence-refresh.js +31 -31
  176. package/dist/conversation-index/backend.js +2 -2
  177. package/dist/corpus-watermark.js +31 -31
  178. package/dist/dashboard-runtime.js +2 -2
  179. package/dist/deep-recall-http-glue.js +9 -9
  180. package/dist/entity-retrieval.js +31 -31
  181. package/dist/explicit-capture.js +7 -7
  182. package/dist/external-wiki-access.js +82 -82
  183. package/dist/extraction-span-materialize.js +2 -2
  184. package/dist/extraction.js +44 -44
  185. package/dist/{graph-edge-decay-IU75NV42.js → graph-edge-decay-2BZTY2IM.js} +5 -5
  186. package/dist/graph-edge-decay-2BZTY2IM.js.map +1 -0
  187. package/dist/graph-jsonl.js +3 -3
  188. package/dist/graph-snapshot.js +3 -3
  189. package/dist/graph.js +2 -2
  190. package/dist/harmonic-retrieval.js +30 -30
  191. package/dist/harmonic-source-lookup.js +2 -2
  192. package/dist/index.js +226 -224
  193. package/dist/lcm/index.js +3 -3
  194. package/dist/local-llm-helpers.d.ts.map +1 -1
  195. package/dist/local-llm-helpers.js +2 -1
  196. package/dist/local-llm-transport.d.ts +1 -0
  197. package/dist/local-llm-transport.d.ts.map +1 -1
  198. package/dist/local-llm-transport.js +5 -3
  199. package/dist/local-llm.js +3 -3
  200. package/dist/location/index.js +32 -32
  201. package/dist/maintenance/memory-governance.js +29 -29
  202. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +29 -29
  203. package/dist/maintenance/rebuild-memory-projection-runner.js +31 -31
  204. package/dist/maintenance/rebuild-memory-projection.js +30 -30
  205. package/dist/memory-browse-operations.js +82 -82
  206. package/dist/memory-browse.js +5 -5
  207. package/dist/memory-subject.js +3 -3
  208. package/dist/namespaces/migrate.js +30 -30
  209. package/dist/namespaces/search.js +11 -11
  210. package/dist/namespaces/storage.js +29 -29
  211. package/dist/offline-sync-changeset-generations.js +3 -3
  212. package/dist/offline-sync-embedding-generation.js +2 -2
  213. package/dist/offline-sync-file-io.js +2 -2
  214. package/dist/offline-sync-impression-drain.js +3 -3
  215. package/dist/offline-sync-runtime-state.d.ts.map +1 -1
  216. package/dist/offline-sync-runtime-state.js +1 -1
  217. package/dist/offline-sync-snapshot-stream.js +8 -8
  218. package/dist/offline-sync-upload-staging.js +3 -3
  219. package/dist/offline-sync.js +8 -8
  220. package/dist/operator-doctor-corpus.js +32 -32
  221. package/dist/operator-toolkit.js +51 -51
  222. package/dist/orchestration/maintenance.js +44 -44
  223. package/dist/orchestrator.js +174 -172
  224. package/dist/qmd.js +4 -4
  225. package/dist/recall-navigation-operations.js +82 -82
  226. package/dist/recall-navigation.js +81 -81
  227. package/dist/recall-state-view-admission.js +5 -5
  228. package/dist/recall-why-service.js +5 -5
  229. package/dist/reconcile/manifest.js +3 -3
  230. package/dist/{resolution-V6JBMW7U.js → resolution-4ZEQPXUZ.js} +6 -6
  231. package/dist/resolution-4ZEQPXUZ.js.map +1 -0
  232. package/dist/resume-bundles.js +41 -41
  233. package/dist/retrieval-agents.js +2 -2
  234. package/dist/salvage-envelope.js +5 -5
  235. package/dist/search/factory.js +10 -10
  236. package/dist/search/index.js +14 -14
  237. package/dist/search/lancedb-backend.js +2 -2
  238. package/dist/search/meilisearch-backend.js +2 -2
  239. package/dist/search/orama-backend.js +2 -2
  240. package/dist/secure-store/index.js +2 -2
  241. package/dist/semantic-consolidation.js +30 -30
  242. package/dist/semantic-rule-promotion.js +29 -29
  243. package/dist/semantic-rule-verifier.js +29 -29
  244. package/dist/source-agent-qualifier.js +34 -34
  245. package/dist/standup/build.js +29 -29
  246. package/dist/storage/buffer-session-files.d.ts +69 -0
  247. package/dist/storage/buffer-session-files.d.ts.map +1 -0
  248. package/dist/storage.d.ts +5 -1
  249. package/dist/storage.d.ts.map +1 -1
  250. package/dist/storage.js +28 -28
  251. package/dist/subject-config.js +4 -4
  252. package/dist/summarizer.js +4 -4
  253. package/dist/support-passport/index.js +84 -84
  254. package/dist/transfer/export-okf.js +30 -30
  255. package/dist/verified-recall.js +29 -29
  256. package/dist/who-knows-cli.js +30 -30
  257. package/dist/who-knows.js +29 -29
  258. package/dist/write-envelope.js +4 -4
  259. package/package.json +2 -2
  260. package/src/buffer-eviction.ts +51 -0
  261. package/src/buffer-incremental-persistence.test.ts +256 -0
  262. package/src/buffer-persistence.ts +89 -0
  263. package/src/buffer.ts +32 -61
  264. package/src/local-llm-headers-timeout.test.ts +64 -0
  265. package/src/local-llm-helpers.ts +6 -1
  266. package/src/local-llm-transport.ts +14 -1
  267. package/src/offline-sync-runtime-state.ts +24 -2
  268. package/src/offline-sync.test.ts +9 -0
  269. package/src/secure-store/secure-fs.ts +11 -0
  270. package/src/secure-store/secure-store.test.ts +22 -0
  271. package/src/storage/buffer-session-files.ts +249 -0
  272. package/src/storage-buffer-sessions.test.ts +369 -0
  273. package/src/storage.ts +11 -16
  274. package/dist/chunk-JUXOTY6J.js.map +0 -1
  275. package/dist/chunk-MLED254J.js.map +0 -1
  276. package/dist/chunk-O3VKQMWC.js.map +0 -1
  277. package/dist/chunk-TYT4TEQV.js.map +0 -1
  278. package/dist/chunk-UAYA4NJN.js.map +0 -1
  279. /package/dist/{auto-sync-VPHR3FBI.js.map → auto-sync-R3XGJL5I.js.map} +0 -0
  280. /package/dist/{graph-edge-decay-IU75NV42.js.map → buffer-eviction.js.map} +0 -0
  281. /package/dist/{resolution-V6JBMW7U.js.map → buffer-persistence.js.map} +0 -0
  282. /package/dist/{chunk-HT6IEMQ2.js.map → chunk-2RJO7VS4.js.map} +0 -0
  283. /package/dist/{chunk-YLF42JQ7.js.map → chunk-3BPMYMPX.js.map} +0 -0
  284. /package/dist/{chunk-GAARPWMN.js.map → chunk-3L5WH6CV.js.map} +0 -0
  285. /package/dist/{chunk-XVZA6VXC.js.map → chunk-3SL52JKD.js.map} +0 -0
  286. /package/dist/{chunk-S2PDFSXS.js.map → chunk-4P4VLPGB.js.map} +0 -0
  287. /package/dist/{chunk-2ZYIAUYU.js.map → chunk-5CWH2LKL.js.map} +0 -0
  288. /package/dist/{chunk-G2PC6IZB.js.map → chunk-5U3NUHMA.js.map} +0 -0
  289. /package/dist/{chunk-4LOMATQA.js.map → chunk-5UWP5X3T.js.map} +0 -0
  290. /package/dist/{chunk-G3T6YMBO.js.map → chunk-64S6X4IU.js.map} +0 -0
  291. /package/dist/{chunk-NHB4RC3G.js.map → chunk-6QOL4G76.js.map} +0 -0
  292. /package/dist/{chunk-GRBQO56D.js.map → chunk-6R6CU3MR.js.map} +0 -0
  293. /package/dist/{chunk-SQ554O3K.js.map → chunk-74AY3S7U.js.map} +0 -0
  294. /package/dist/{chunk-4ISHIPOC.js.map → chunk-7PG7QBWD.js.map} +0 -0
  295. /package/dist/{chunk-A4B7WL5N.js.map → chunk-7ZBANI3M.js.map} +0 -0
  296. /package/dist/{chunk-DG5UKN2Y.js.map → chunk-A23DPFFI.js.map} +0 -0
  297. /package/dist/{chunk-77ZK533I.js.map → chunk-ARFT6ZBE.js.map} +0 -0
  298. /package/dist/{chunk-V44DWTFA.js.map → chunk-AYXRK264.js.map} +0 -0
  299. /package/dist/{chunk-GRZX3RHY.js.map → chunk-BPBUUGAO.js.map} +0 -0
  300. /package/dist/{chunk-DFP37GUN.js.map → chunk-CCYXCT4B.js.map} +0 -0
  301. /package/dist/{chunk-JJWOKRZA.js.map → chunk-CNQQNYJ2.js.map} +0 -0
  302. /package/dist/{chunk-QZP3RMYP.js.map → chunk-CU7WDDPV.js.map} +0 -0
  303. /package/dist/{chunk-GY4R4P2A.js.map → chunk-CUWOSRCS.js.map} +0 -0
  304. /package/dist/{chunk-CJKHLACF.js.map → chunk-D2AKUKAQ.js.map} +0 -0
  305. /package/dist/{chunk-BRZRP5WD.js.map → chunk-DK5VMJL3.js.map} +0 -0
  306. /package/dist/{chunk-77IXBNV6.js.map → chunk-E24RUQEN.js.map} +0 -0
  307. /package/dist/{chunk-PTQZPVRQ.js.map → chunk-E5PVMB3I.js.map} +0 -0
  308. /package/dist/{chunk-AHS3QEEA.js.map → chunk-EEOYF3IB.js.map} +0 -0
  309. /package/dist/{chunk-PH33R7GN.js.map → chunk-EHIKY2IA.js.map} +0 -0
  310. /package/dist/{chunk-NLQBTHBV.js.map → chunk-EVVCE7S2.js.map} +0 -0
  311. /package/dist/{chunk-FQ52AJXX.js.map → chunk-FBAHBFT5.js.map} +0 -0
  312. /package/dist/{chunk-D75ET36O.js.map → chunk-FL6SLVAJ.js.map} +0 -0
  313. /package/dist/{chunk-6NAN4WFY.js.map → chunk-FO5AFCRI.js.map} +0 -0
  314. /package/dist/{chunk-5LWODP5R.js.map → chunk-FRAL3EU4.js.map} +0 -0
  315. /package/dist/{chunk-AAANTXCQ.js.map → chunk-FZVZYU5C.js.map} +0 -0
  316. /package/dist/{chunk-GQVTALUJ.js.map → chunk-G2SRB62M.js.map} +0 -0
  317. /package/dist/{chunk-ORWDCX5Q.js.map → chunk-G6PPA4TH.js.map} +0 -0
  318. /package/dist/{chunk-VNAIUNXI.js.map → chunk-GCVUBVEN.js.map} +0 -0
  319. /package/dist/{chunk-TGAXRT7N.js.map → chunk-GFYFKXWZ.js.map} +0 -0
  320. /package/dist/{chunk-ZABEDRGQ.js.map → chunk-HIMXDRSQ.js.map} +0 -0
  321. /package/dist/{chunk-GDYLZ6FR.js.map → chunk-HJ3UTGIW.js.map} +0 -0
  322. /package/dist/{chunk-MGXLLL5L.js.map → chunk-HKEK27M4.js.map} +0 -0
  323. /package/dist/{chunk-X3IFRQKS.js.map → chunk-I73ZSORJ.js.map} +0 -0
  324. /package/dist/{chunk-XUB4I2N6.js.map → chunk-JFI3ATK4.js.map} +0 -0
  325. /package/dist/{chunk-R4SATHCV.js.map → chunk-JH2ALMPR.js.map} +0 -0
  326. /package/dist/{chunk-TKBW4YLS.js.map → chunk-JH4FCVWJ.js.map} +0 -0
  327. /package/dist/{chunk-X73SCHKR.js.map → chunk-KC5SVCUJ.js.map} +0 -0
  328. /package/dist/{chunk-6NXHX6MI.js.map → chunk-KOY25HUB.js.map} +0 -0
  329. /package/dist/{chunk-TRW7N4FS.js.map → chunk-L2JQSG5M.js.map} +0 -0
  330. /package/dist/{chunk-SIIWHIBK.js.map → chunk-L46Y5FPE.js.map} +0 -0
  331. /package/dist/{chunk-CIJRM6SQ.js.map → chunk-LAQS5HYU.js.map} +0 -0
  332. /package/dist/{chunk-23VGZ65V.js.map → chunk-LHBOFROT.js.map} +0 -0
  333. /package/dist/{chunk-LO775P3S.js.map → chunk-LRJKHXU4.js.map} +0 -0
  334. /package/dist/{chunk-QISYYK4L.js.map → chunk-LSSCMZQ2.js.map} +0 -0
  335. /package/dist/{chunk-PCIIKQXW.js.map → chunk-MBMT5FUQ.js.map} +0 -0
  336. /package/dist/{chunk-XFDHL252.js.map → chunk-MGXFJOUR.js.map} +0 -0
  337. /package/dist/{chunk-7WTTE5FW.js.map → chunk-MH3LC7KB.js.map} +0 -0
  338. /package/dist/{chunk-ERMT5WQM.js.map → chunk-MKE5DP3S.js.map} +0 -0
  339. /package/dist/{chunk-DJMC4W6N.js.map → chunk-MNDHKZZQ.js.map} +0 -0
  340. /package/dist/{chunk-7FG2JSU6.js.map → chunk-NMKETOAX.js.map} +0 -0
  341. /package/dist/{chunk-TVM47JQB.js.map → chunk-NQOM6PHO.js.map} +0 -0
  342. /package/dist/{chunk-AI2VXOSO.js.map → chunk-NSQSOLCE.js.map} +0 -0
  343. /package/dist/{chunk-UR25IQVT.js.map → chunk-P5CUEB2Q.js.map} +0 -0
  344. /package/dist/{chunk-FXO43NBW.js.map → chunk-PSQ2P2T3.js.map} +0 -0
  345. /package/dist/{chunk-KVNOHMVE.js.map → chunk-PTRPOJ7U.js.map} +0 -0
  346. /package/dist/{chunk-SXRX6K35.js.map → chunk-QEGE5QCL.js.map} +0 -0
  347. /package/dist/{chunk-CM6JDFCN.js.map → chunk-QIC7YZ3D.js.map} +0 -0
  348. /package/dist/{chunk-KRAMCBIW.js.map → chunk-QKXPOLQD.js.map} +0 -0
  349. /package/dist/{chunk-G5UR4DXQ.js.map → chunk-RBC4GIZU.js.map} +0 -0
  350. /package/dist/{chunk-GK56GNNS.js.map → chunk-RVYXSS3Q.js.map} +0 -0
  351. /package/dist/{chunk-G7XAZU2L.js.map → chunk-RWWYBE7E.js.map} +0 -0
  352. /package/dist/{chunk-HJM23E65.js.map → chunk-S53WZWGL.js.map} +0 -0
  353. /package/dist/{chunk-3ODEGTVV.js.map → chunk-TDCR6PQL.js.map} +0 -0
  354. /package/dist/{chunk-QCP75JGO.js.map → chunk-TFA6HPOA.js.map} +0 -0
  355. /package/dist/{chunk-SE3JAD22.js.map → chunk-TPKB7T6Z.js.map} +0 -0
  356. /package/dist/{chunk-6VYY2XFK.js.map → chunk-TVIGN3XZ.js.map} +0 -0
  357. /package/dist/{chunk-K567EIW3.js.map → chunk-U4JXQCJ3.js.map} +0 -0
  358. /package/dist/{chunk-WI6MIJYK.js.map → chunk-U7HJBODU.js.map} +0 -0
  359. /package/dist/{chunk-IS4HKNWP.js.map → chunk-UBN5AOLT.js.map} +0 -0
  360. /package/dist/{chunk-7VJEZQYF.js.map → chunk-UFBIOF7C.js.map} +0 -0
  361. /package/dist/{chunk-JNELS2TW.js.map → chunk-UJDTXWMT.js.map} +0 -0
  362. /package/dist/{chunk-M3GKSCFN.js.map → chunk-ULUX6Q7R.js.map} +0 -0
  363. /package/dist/{chunk-ZRU7636R.js.map → chunk-UMFBHYPW.js.map} +0 -0
  364. /package/dist/{chunk-GKNX4QO3.js.map → chunk-UQWA5QDT.js.map} +0 -0
  365. /package/dist/{chunk-4Z7O2QVJ.js.map → chunk-USOXPN5H.js.map} +0 -0
  366. /package/dist/{chunk-XA32GZ6B.js.map → chunk-UXXVOA4D.js.map} +0 -0
  367. /package/dist/{chunk-OLAQWYNF.js.map → chunk-V5RJCXLG.js.map} +0 -0
  368. /package/dist/{chunk-A2D3WICT.js.map → chunk-VGFNTY3Y.js.map} +0 -0
  369. /package/dist/{chunk-EH6KLYIE.js.map → chunk-VKTP3TZB.js.map} +0 -0
  370. /package/dist/{chunk-CSGFGQBI.js.map → chunk-VPO53QMG.js.map} +0 -0
  371. /package/dist/{chunk-P2RCFQA6.js.map → chunk-WADPRAZE.js.map} +0 -0
  372. /package/dist/{chunk-IVYAPAHG.js.map → chunk-WRDFYSUC.js.map} +0 -0
  373. /package/dist/{chunk-FW7KWZBI.js.map → chunk-WSLNC5AL.js.map} +0 -0
  374. /package/dist/{chunk-C6S2Y7HE.js.map → chunk-XKDQTDXN.js.map} +0 -0
  375. /package/dist/{chunk-4AHKNZW7.js.map → chunk-XLEP7SS3.js.map} +0 -0
  376. /package/dist/{chunk-B6L7BKA7.js.map → chunk-XRFLR3XF.js.map} +0 -0
  377. /package/dist/{chunk-H7QC7WCU.js.map → chunk-XTBEXZFN.js.map} +0 -0
  378. /package/dist/{chunk-S2S4U4G3.js.map → chunk-Y6Y3TRBN.js.map} +0 -0
  379. /package/dist/{chunk-H6SKBCNI.js.map → chunk-YC2C67F7.js.map} +0 -0
  380. /package/dist/{chunk-6FIFEXFF.js.map → chunk-YGYT7S3G.js.map} +0 -0
  381. /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
  382. /package/dist/{chunk-YOL56HY5.js.map → chunk-YQ6SLFNZ.js.map} +0 -0
  383. /package/dist/{chunk-G22A6WJQ.js.map → chunk-Z2T5BPOZ.js.map} +0 -0
  384. /package/dist/{chunk-IHTKBQBS.js.map → chunk-Z5AQZRHF.js.map} +0 -0
  385. /package/dist/{chunk-QQ4T622E.js.map → chunk-Z5QWUR3Y.js.map} +0 -0
@@ -34,11 +34,33 @@ function isRemoteAuthoritativeRuntimeShard(basename: string, parts: string[]): b
34
34
  return EMBEDDING_SHARD_FILE_PATTERN.test(basename) && parts[parts.length - 2] === "embeddings";
35
35
  }
36
36
 
37
+ /**
38
+ * Per-session buffer state files (`state/buffer/<session>.json`) are
39
+ * remote-authoritative like the legacy single `state/buffer.json` they
40
+ * replaced: the incoming copy wins so two nodes do not each keep their own
41
+ * version of the same session's buffered turns.
42
+ */
43
+ function isRemoteAuthoritativeBufferSessionFile(parts: string[]): boolean {
44
+ const basename = parts[parts.length - 1] ?? "";
45
+ // Matches both `state/buffer/<session>.json` and the namespace-scoped
46
+ // `namespaces/<ns>/state/buffer/<session>.json`.
47
+ return (
48
+ parts.length >= 3 &&
49
+ parts[parts.length - 2] === "buffer" &&
50
+ parts[parts.length - 3] === "state" &&
51
+ basename.endsWith(".json")
52
+ );
53
+ }
54
+
37
55
  export function shouldPreferIncomingOfflineRuntimeFile(relPosix: string): boolean {
38
56
  const parts = relPosix.split("/");
39
57
  const basename = parts[parts.length - 1] ?? "";
40
- return isCanonicalRuntimeStatePath(parts) &&
41
- (REMOTE_AUTHORITATIVE_RUNTIME_STATE_FILES.has(basename) || isRemoteAuthoritativeRuntimeShard(basename, parts));
58
+ return (
59
+ isCanonicalRuntimeStatePath(parts) &&
60
+ (REMOTE_AUTHORITATIVE_RUNTIME_STATE_FILES.has(basename) ||
61
+ isRemoteAuthoritativeRuntimeShard(basename, parts) ||
62
+ isRemoteAuthoritativeBufferSessionFile(parts))
63
+ );
42
64
  }
43
65
 
44
66
  export function shouldDeleteAbsentIncomingOfflineRuntimeFile(relPosix: string): boolean {
@@ -506,6 +506,15 @@ test("offline sync push-side default excludes live LCM sqlite but apply-side sti
506
506
  assert.equal(shouldPreferIncomingOfflineRuntimeFile("state/embeddings/other.json"), false);
507
507
  // Shard preference is scoped to the embedding index directory.
508
508
  assert.equal(shouldPreferIncomingOfflineRuntimeFile("state/other/shard-0000.json"), false);
509
+ // Per-session buffer files are remote-authoritative like the legacy
510
+ // single state/buffer.json they replaced.
511
+ assert.equal(shouldPreferIncomingOfflineRuntimeFile("state/buffer/session-a.json"), true);
512
+ assert.equal(
513
+ shouldPreferIncomingOfflineRuntimeFile("namespaces/team/state/buffer/session-a.json"),
514
+ true,
515
+ );
516
+ assert.equal(shouldPreferIncomingOfflineRuntimeFile("state/buffer/notes.txt"), false);
517
+ assert.equal(shouldPreferIncomingOfflineRuntimeFile("state/buffer-other/session-a.json"), false);
509
518
 
510
519
  // buildOfflineSyncSnapshotForPaths still rejects explicitly excluded
511
520
  // paths so callers cannot bypass the default exclude via path lists.
@@ -836,6 +836,7 @@ function isEncryptableStoragePath(filePath: string, rootDir: string): boolean {
836
836
  const normalized = normalizeStorageRelativePath(rel);
837
837
  if (normalized === "profile.md") return true;
838
838
  if (isEncryptableStateSidecar(normalized)) return true;
839
+ if (isEncryptableBufferSessionFile(normalized)) return true;
839
840
  if (isEncryptableSummarySidecar(normalized)) return true;
840
841
  const firstSegment = normalized.split("/", 1)[0];
841
842
  return ENCRYPTABLE_MARKDOWN_STORAGE_ROOTS.has(firstSegment) && normalized.endsWith(".md");
@@ -904,6 +905,16 @@ function isEncryptableStateSidecar(normalized: string): boolean {
904
905
  return ENCRYPTABLE_STATE_SIDECARS.has(normalized);
905
906
  }
906
907
 
908
+ /**
909
+ * Per-session buffer state files (`state/buffer/<session>.json`) hold the same
910
+ * buffered turns the legacy `state/buffer.json` held, so they are encryptable
911
+ * state sidecars too — otherwise enabling the secure store would leave the
912
+ * daemon's buffered turns in plaintext on disk.
913
+ */
914
+ function isEncryptableBufferSessionFile(normalized: string): boolean {
915
+ return normalized.startsWith("state/buffer/") && normalized.endsWith(".json");
916
+ }
917
+
907
918
  function isEncryptableSummarySidecar(normalized: string): boolean {
908
919
  return normalized.startsWith("summaries/") && normalized.endsWith(".json");
909
920
  }
@@ -620,6 +620,28 @@ test("secure-store migration keeps namespaced file AAD readable through memory r
620
620
  }
621
621
  });
622
622
 
623
+ test("secure-store migration encrypts per-session buffer state files", async () => {
624
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-secure-store-buffer-session-"));
625
+ try {
626
+ const sessionPath = path.join(memoryDir, "state", "buffer", "session-a.json");
627
+ await mkdir(path.dirname(sessionPath), { recursive: true });
628
+ await writeFile(sessionPath, JSON.stringify({ key: "session-a", turns: [] }), "utf8");
629
+
630
+ const key = deriveKeyScrypt("buffer-session", Buffer.alloc(KDF_SALT_LENGTH, 0x66), FAST_SCRYPT);
631
+ const migrated = await migrateMemoryDirToEncrypted(memoryDir, key);
632
+
633
+ assert.equal(migrated.encrypted, 1);
634
+ assert.deepEqual(migrated.errors, []);
635
+ assert.equal(isEncryptedFile(await readFile(sessionPath)), true);
636
+ assert.equal(
637
+ await readMaybeEncryptedFile(sessionPath, key, memoryDir),
638
+ JSON.stringify({ key: "session-a", turns: [] }),
639
+ );
640
+ } finally {
641
+ await rm(memoryDir, { recursive: true, force: true });
642
+ }
643
+ });
644
+
623
645
  test("secure-store migration treats missing memory roots as empty", async () => {
624
646
  const tempRoot = await mkdtemp(path.join(tmpdir(), "remnic-secure-store-missing-root-"));
625
647
  try {
@@ -0,0 +1,249 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { Stats } from "node:fs";
3
+ import { lstat, readdir, unlink } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { log } from "../logger.js";
6
+ import { SecureStoreDecryptError, SecureStoreLockedError } from "../secure-store/secure-fs.js";
7
+ import type { BufferEntryState, BufferState, BufferTurn } from "../types.js";
8
+
9
+ /** The secure-store file I/O the owning StorageManager already provides. */
10
+ export interface BufferFileIo {
11
+ read(filePath: string): Promise<string>;
12
+ write(filePath: string, content: string): Promise<void>;
13
+ }
14
+
15
+ type FsError = NodeJS.ErrnoException;
16
+
17
+ function isMissing(err: FsError | null): boolean {
18
+ return err?.code === "ENOENT";
19
+ }
20
+
21
+ function asFsError(err: Error): FsError {
22
+ return err;
23
+ }
24
+
25
+ /**
26
+ * Case-independent, collision-free file name for a buffer key: the lowercase
27
+ * hex SHA-256 of the key under a fixed prefix. Hex has no case and no path
28
+ * characters, so `Session-A` and `session-a` get different names even on
29
+ * case-insensitive filesystems, every key (short or long) lives in the same
30
+ * naming domain, and the key itself is stored inside the file (loading never
31
+ * inverts the hash).
32
+ */
33
+ export function bufferSessionFileName(key: string): string {
34
+ return `s-${createHash("sha256").update(key).digest("hex")}.json`;
35
+ }
36
+
37
+ /** Flatten a legacy BufferState into per-session entries (top-level fields
38
+ * are the legacy mirror of the "default" entry). */
39
+ export function legacyStateToEntries(state: BufferState): Map<string, BufferEntryState> {
40
+ const entries = new Map<string, BufferEntryState>();
41
+ for (const [key, value] of Object.entries(state.entries ?? {})) {
42
+ if (value && Array.isArray(value.turns)) entries.set(key, value);
43
+ }
44
+ if (!entries.has("default")) {
45
+ entries.set("default", {
46
+ turns: Array.isArray(state.turns) ? state.turns : [],
47
+ lastExtractionAt: state.lastExtractionAt ?? null,
48
+ extractionCount: Number.isFinite(state.extractionCount) ? state.extractionCount : 0,
49
+ });
50
+ }
51
+ return entries;
52
+ }
53
+
54
+ /** What a per-session file contains once parsed (written only by this module). */
55
+ interface SessionFileBody {
56
+ key?: string;
57
+ turns?: BufferTurn[];
58
+ retainedTurns?: BufferTurn[];
59
+ lastExtractionAt?: string | null;
60
+ extractionCount?: number;
61
+ }
62
+
63
+ function entryFromBody(body: SessionFileBody): { key: string; entry: BufferEntryState } | null {
64
+ if (!body || !Array.isArray(body.turns) || (body.key?.length ?? 0) === 0) return null;
65
+ const entry: BufferEntryState = {
66
+ turns: body.turns,
67
+ lastExtractionAt: body.lastExtractionAt ?? null,
68
+ extractionCount: Number.isFinite(body.extractionCount) ? Number(body.extractionCount) : 0,
69
+ };
70
+ if (Array.isArray(body.retainedTurns)) entry.retainedTurns = body.retainedTurns;
71
+ return { key: String(body.key), entry };
72
+ }
73
+
74
+ /**
75
+ * One file per buffer session under `<stateDir>/buffer/`, so the observe hot
76
+ * path persists only the session that changed. All writes happen under
77
+ * SmartBuffer's serialized mutation chain (one writer per process).
78
+ */
79
+ export class BufferSessionFiles {
80
+ private dirVerified = false;
81
+
82
+ constructor(
83
+ private readonly stateDir: string,
84
+ private readonly io: BufferFileIo,
85
+ ) {}
86
+
87
+ private get dir(): string {
88
+ return path.join(this.stateDir, "buffer");
89
+ }
90
+
91
+ private fileFor(key: string): string {
92
+ return path.join(this.dir, bufferSessionFileName(key));
93
+ }
94
+
95
+ /**
96
+ * Refuse a symlinked buffer directory: following it would send buffered
97
+ * conversation data (and deletions) outside the memory root. One lstat of
98
+ * one directory, cached after it passes, so the observe path pays nothing
99
+ * per write; the cache is dropped on any failure.
100
+ */
101
+ private async assertDirSafe(): Promise<void> {
102
+ if (this.dirVerified) return;
103
+ let stat: Stats | null = null;
104
+ try {
105
+ stat = await lstat(this.dir);
106
+ } catch (err) {
107
+ if (!(err instanceof Error) || !isMissing(asFsError(err))) throw err;
108
+ }
109
+ if (stat && (stat.isSymbolicLink() || !stat.isDirectory())) {
110
+ throw new Error(`buffer session directory must be a real directory, not a symlink or file: ${this.dir}`);
111
+ }
112
+ this.dirVerified = stat !== null;
113
+ }
114
+
115
+ /** Persist ONE session atomically. Compact JSON: machine-read state, and
116
+ * pretty-printing a large buffer costs CPU and bytes on every observe. */
117
+ async save(key: string, entry: BufferEntryState): Promise<void> {
118
+ await this.assertDirSafe();
119
+ await this.io.write(this.fileFor(key), JSON.stringify({ key, ...entry }));
120
+ }
121
+
122
+ /** Remove one session's file. Idempotent: an absent file is fine. */
123
+ async remove(key: string): Promise<void> {
124
+ await this.assertDirSafe();
125
+ try {
126
+ await unlink(this.fileFor(key));
127
+ } catch (err) {
128
+ if (!(err instanceof Error) || !isMissing(asFsError(err))) throw err;
129
+ }
130
+ }
131
+
132
+ private async listFiles(): Promise<string[]> {
133
+ await this.assertDirSafe();
134
+ try {
135
+ const dirents = await readdir(this.dir, { withFileTypes: true });
136
+ // Regular files only: a symlink inside the directory is never followed.
137
+ return dirents.filter((d) => d.isFile() && d.name.endsWith(".json")).map((d) => d.name);
138
+ } catch (err) {
139
+ if (err instanceof Error && isMissing(asFsError(err))) return [];
140
+ throw err;
141
+ }
142
+ }
143
+
144
+ /** Read every per-session file. Corrupt files are skipped (and logged) so
145
+ * one bad file cannot nuke the other sessions' buffered turns. */
146
+ private async readAll(): Promise<{ entries: Map<string, BufferEntryState>; fileCount: number }> {
147
+ const names = await this.listFiles();
148
+ const entries = new Map<string, BufferEntryState>();
149
+ for (const name of names) {
150
+ try {
151
+ const body: SessionFileBody = JSON.parse(await this.io.read(path.join(this.dir, name)));
152
+ const parsed = entryFromBody(body);
153
+ if (!parsed) throw new Error("invalid buffer session file shape");
154
+ if (entries.has(parsed.key)) {
155
+ log.warn(`buffer: duplicate session file for key ${parsed.key} (${name}); keeping the first`);
156
+ continue;
157
+ }
158
+ entries.set(parsed.key, parsed.entry);
159
+ } catch (err) {
160
+ // A locked store or a wrong key is not a corrupt file: reading it as empty would let
161
+ // later saves replace the real encrypted session.
162
+ if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
163
+ log.warn(`buffer: skipping unreadable per-session buffer file ${name}: ${err instanceof Error ? err.message : String(err)}`);
164
+ }
165
+ }
166
+ return { entries, fileCount: names.length };
167
+ }
168
+
169
+ /**
170
+ * Read the legacy single-file buffer.json for migration. Null when absent,
171
+ * or unreadable while session files exist (they are authoritative; a
172
+ * corrupt leftover must not block startup). With no session files a corrupt
173
+ * legacy file THROWS, preserving the old contract so callers can reset it.
174
+ */
175
+ private async readLegacy(hasSessionFiles: boolean): Promise<BufferState | null> {
176
+ let raw: string;
177
+ try {
178
+ raw = await this.io.read(path.join(this.stateDir, "buffer.json"));
179
+ } catch (err) {
180
+ if (err instanceof SecureStoreLockedError) throw err;
181
+ if (err instanceof Error && isMissing(asFsError(err))) return null;
182
+ throw err;
183
+ }
184
+ try {
185
+ const legacy: BufferState = JSON.parse(raw);
186
+ return legacy;
187
+ } catch (err) {
188
+ if (!hasSessionFiles) throw err;
189
+ log.warn(
190
+ `buffer: legacy buffer.json is unreadable but per-session buffer files exist; ` +
191
+ `ignoring the legacy file (${err instanceof Error ? err.message : String(err)})`,
192
+ );
193
+ return null;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Load the whole buffer, migrating a legacy buffer.json once. A per-session
199
+ * file that parsed wins over the legacy snapshot (a crash mid-migration
200
+ * leaves exactly that pair); legacy entries without a usable file are
201
+ * written out, then the legacy file is retired. Both steps are idempotent.
202
+ */
203
+ async load(): Promise<BufferState> {
204
+ const { entries, fileCount } = await this.readAll();
205
+ const legacy = await this.readLegacy(fileCount > 0);
206
+ if (legacy !== null) {
207
+ for (const [key, entry] of legacyStateToEntries(legacy)) {
208
+ if (entries.has(key)) continue;
209
+ entries.set(key, entry);
210
+ await this.save(key, entry);
211
+ }
212
+ try {
213
+ await unlink(path.join(this.stateDir, "buffer.json"));
214
+ } catch (err) {
215
+ if (!(err instanceof Error) || !isMissing(asFsError(err))) throw err;
216
+ }
217
+ }
218
+ const out: NonNullable<BufferState["entries"]> = Object.create(null);
219
+ for (const [key, entry] of entries) out[key] = entry;
220
+ const def = entries.get("default");
221
+ return {
222
+ turns: def?.turns ?? [],
223
+ lastExtractionAt: def?.lastExtractionAt ?? null,
224
+ extractionCount: def?.extractionCount ?? 0,
225
+ entries: out,
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Reconcile the files with a WHOLE buffer state: write every entry, then
231
+ * delete files for keys no longer present. Writes first, deletes last: a
232
+ * crash leaves stale extra files (removed by the next reconcile) but never
233
+ * loses sessions the state says exist. Not on the observe hot path.
234
+ */
235
+ async reconcile(state: BufferState): Promise<void> {
236
+ const entries = legacyStateToEntries(state);
237
+ const existing = await this.listFiles();
238
+ for (const [key, entry] of entries) await this.save(key, entry);
239
+ const live = new Set([...entries.keys()].map((key) => bufferSessionFileName(key)));
240
+ for (const name of existing) {
241
+ if (live.has(name)) continue;
242
+ try {
243
+ await unlink(path.join(this.dir, name));
244
+ } catch (err) {
245
+ if (!(err instanceof Error) || !isMissing(asFsError(err))) throw err;
246
+ }
247
+ }
248
+ }
249
+ }