@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
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Per-session buffer persistence (storage layer).
3
+ *
4
+ * The legacy buffer persistence rewrote the WHOLE state/buffer.json — every
5
+ * session's turns — on every buffer save, which on long-running daemons meant
6
+ * serializing 100+ MB per observe. These tests pin the replacement contract:
7
+ *
8
+ * - one file per session under `<memoryDir>/state/buffer/`,
9
+ * - incremental `saveBufferSession` / `deleteBufferSession` for the hot path,
10
+ * - whole-state `saveBuffer` as a full reconcile (used at correctness
11
+ * boundaries and by legacy callers),
12
+ * - one-time migration from the legacy single-file format, safe when a crash
13
+ * lands mid-migration (per-session file wins over the legacy snapshot),
14
+ * - per-session writes do NOT run the heavyweight `ensureDirectories()`
15
+ * pass (the realpath/lstat storm) — they mkdir only what the write needs.
16
+ */
17
+ import assert from "node:assert/strict";
18
+ import { mkdir, mkdtemp, readdir, readFile, rm, stat, symlink, writeFile } from "node:fs/promises";
19
+ import os from "node:os";
20
+ import path from "node:path";
21
+ import test from "node:test";
22
+
23
+ import { SecureStoreDecryptError } from "./secure-store/secure-fs.js";
24
+ import { StorageManager } from "./storage.js";
25
+ import { bufferSessionFileName } from "./storage/buffer-session-files.js";
26
+ import type { BufferEntryState, BufferState } from "./types.js";
27
+
28
+ async function tempRoot(prefix: string): Promise<string> {
29
+ return mkdtemp(path.join(os.tmpdir(), `${prefix}-`));
30
+ }
31
+
32
+ function makeEntry(turnContent: string, extractionCount = 0): BufferEntryState {
33
+ return {
34
+ turns: [
35
+ {
36
+ role: "user",
37
+ content: turnContent,
38
+ timestamp: "2026-10-29T12:00:00.000Z",
39
+ sessionKey: "session-a",
40
+ },
41
+ ],
42
+ lastExtractionAt: extractionCount > 0 ? "2026-10-29T12:01:00.000Z" : null,
43
+ extractionCount,
44
+ };
45
+ }
46
+
47
+ test("saveBufferSession writes one file per session and loadBuffer reads it back", async () => {
48
+ const root = await tempRoot("bufio-sessions");
49
+ try {
50
+ const storage = new StorageManager(root);
51
+ await storage.saveBufferSession("session-a", makeEntry("alpha fact"));
52
+
53
+ const bufferDir = path.join(root, "state", "buffer");
54
+ const names = await readdir(bufferDir);
55
+ assert.equal(names.length, 1, `expected exactly one session file, got ${names.join(",")}`);
56
+
57
+ const state = await storage.loadBuffer();
58
+ assert.equal(state.entries?.["session-a"]?.turns.length, 1);
59
+ assert.equal(state.entries?.["session-a"]?.turns[0]?.content, "alpha fact");
60
+ } finally {
61
+ await rm(root, { recursive: true, force: true });
62
+ }
63
+ });
64
+
65
+ test("saving session B leaves session A's file byte-identical", async () => {
66
+ const root = await tempRoot("bufio-isolation");
67
+ try {
68
+ const storage = new StorageManager(root);
69
+ await storage.saveBufferSession("session-a", makeEntry("alpha fact"));
70
+ const dir = path.join(root, "state", "buffer");
71
+ const [aName] = await readdir(dir);
72
+ const before = await readFile(path.join(dir, aName));
73
+ const beforeStat = await stat(path.join(dir, aName));
74
+
75
+ await storage.saveBufferSession("session-b", makeEntry("beta fact"));
76
+ const after = await readFile(path.join(dir, aName));
77
+ const afterStat = await stat(path.join(dir, aName));
78
+ assert.deepEqual(after, before, "session A file content changed when session B was saved");
79
+ assert.equal(afterStat.mtimeMs, beforeStat.mtimeMs, "session A file was rewritten when session B was saved");
80
+ } finally {
81
+ await rm(root, { recursive: true, force: true });
82
+ }
83
+ });
84
+
85
+ test("deleteBufferSession removes the file and loadBuffer drops the session", async () => {
86
+ const root = await tempRoot("bufio-delete");
87
+ try {
88
+ const storage = new StorageManager(root);
89
+ await storage.saveBufferSession("session-a", makeEntry("alpha fact"));
90
+ await storage.deleteBufferSession("session-a");
91
+ const state = await storage.loadBuffer();
92
+ assert.equal(state.entries?.["session-a"], undefined);
93
+ // Deleting an absent session is a no-op, not a throw (idempotent cleanup).
94
+ await storage.deleteBufferSession("session-a");
95
+ } finally {
96
+ await rm(root, { recursive: true, force: true });
97
+ }
98
+ });
99
+
100
+ test("legacy state/buffer.json migrates to per-session files exactly once", async () => {
101
+ const root = await tempRoot("bufio-migrate");
102
+ try {
103
+ const stateDir = path.join(root, "state");
104
+ await mkdir(stateDir, { recursive: true });
105
+ const legacy: BufferState = {
106
+ turns: [{ role: "user", content: "default turn", timestamp: "2026-10-29T12:00:00.000Z", sessionKey: "default" }],
107
+ lastExtractionAt: null,
108
+ extractionCount: 2,
109
+ entries: {
110
+ default: {
111
+ turns: [{ role: "user", content: "default turn", timestamp: "2026-10-29T12:00:00.000Z", sessionKey: "default" }],
112
+ lastExtractionAt: null,
113
+ extractionCount: 2,
114
+ },
115
+ "session-a": makeEntry("alpha fact"),
116
+ },
117
+ };
118
+ await writeFile(path.join(stateDir, "buffer.json"), JSON.stringify(legacy), "utf8");
119
+
120
+ const storage = new StorageManager(root);
121
+ const loaded = await storage.loadBuffer();
122
+ assert.equal(loaded.entries?.["session-a"]?.turns[0]?.content, "alpha fact");
123
+ assert.equal(loaded.entries?.default?.extractionCount, 2);
124
+
125
+ // Migration is materialized: per-session files exist, the legacy file is gone.
126
+ const names = await readdir(path.join(stateDir, "buffer"));
127
+ assert.equal(names.filter((n) => n.endsWith(".json")).length, 2, "expected one file per legacy entry");
128
+ await assert.rejects(stat(path.join(stateDir, "buffer.json")), { code: "ENOENT" });
129
+
130
+ // Second load reads purely from the per-session files.
131
+ const reloaded = await storage.loadBuffer();
132
+ assert.equal(reloaded.entries?.["session-a"]?.turns[0]?.content, "alpha fact");
133
+ } finally {
134
+ await rm(root, { recursive: true, force: true });
135
+ }
136
+ });
137
+
138
+ test("crash mid-migration: existing per-session file wins over the legacy snapshot", async () => {
139
+ const root = await tempRoot("bufio-migrate-crash");
140
+ try {
141
+ const stateDir = path.join(root, "state");
142
+ await mkdir(path.join(stateDir, "buffer"), { recursive: true });
143
+ // Session A was already migrated (with a NEWER turn) before the crash;
144
+ // the legacy file still holds the older snapshot for both sessions.
145
+ await storage_writeSession(stateDir, "session-a", makeEntry("alpha fact NEW"));
146
+ const legacy: BufferState = {
147
+ turns: [],
148
+ lastExtractionAt: null,
149
+ extractionCount: 0,
150
+ entries: {
151
+ "session-a": makeEntry("alpha fact OLD"),
152
+ "session-b": makeEntry("beta fact"),
153
+ },
154
+ };
155
+ await writeFile(path.join(stateDir, "buffer.json"), JSON.stringify(legacy), "utf8");
156
+
157
+ const storage = new StorageManager(root);
158
+ const loaded = await storage.loadBuffer();
159
+ assert.equal(loaded.entries?.["session-a"]?.turns[0]?.content, "alpha fact NEW");
160
+ assert.equal(loaded.entries?.["session-b"]?.turns[0]?.content, "beta fact");
161
+ await assert.rejects(stat(path.join(stateDir, "buffer.json")), { code: "ENOENT" });
162
+ } finally {
163
+ await rm(root, { recursive: true, force: true });
164
+ }
165
+ });
166
+
167
+ async function storage_writeSession(stateDir: string, key: string, entry: BufferEntryState): Promise<void> {
168
+ // Written directly (pre-migration crash simulation) using the same file naming
169
+ // the storage layer derives; the storage instance is created after setup.
170
+ await writeFile(
171
+ path.join(stateDir, "buffer", bufferSessionFileName(key)),
172
+ JSON.stringify({ key, ...entry }),
173
+ "utf8",
174
+ );
175
+ }
176
+
177
+ test("whole-state saveBuffer reconciles per-session files (writes, deletes stale)", async () => {
178
+ const root = await tempRoot("bufio-reconcile");
179
+ try {
180
+ const storage = new StorageManager(root);
181
+ await storage.saveBufferSession("session-a", makeEntry("alpha fact"));
182
+ await storage.saveBufferSession("session-gone", makeEntry("stale fact"));
183
+
184
+ await storage.saveBuffer({
185
+ turns: [],
186
+ lastExtractionAt: null,
187
+ extractionCount: 0,
188
+ entries: { "session-a": makeEntry("alpha fact v2") },
189
+ });
190
+
191
+ const state = await storage.loadBuffer();
192
+ assert.equal(state.entries?.["session-a"]?.turns[0]?.content, "alpha fact v2");
193
+ assert.equal(state.entries?.["session-gone"], undefined, "stale session file survived a whole-state reconcile");
194
+ const names = await readdir(path.join(root, "state", "buffer"));
195
+ // Reconcile materializes the "default" entry too (legacy semantics: the
196
+ // top-level state IS the default session), so expect exactly those two.
197
+ assert.deepEqual(
198
+ names.filter((n) => n.endsWith(".json")).sort(),
199
+ [bufferSessionFileName("session-a"), bufferSessionFileName("default")].sort(),
200
+ );
201
+ } finally {
202
+ await rm(root, { recursive: true, force: true });
203
+ }
204
+ });
205
+
206
+ test("a corrupt per-session file is skipped; the rest of the buffer loads", async () => {
207
+ const root = await tempRoot("bufio-corrupt-file");
208
+ try {
209
+ const storage = new StorageManager(root);
210
+ await storage.saveBufferSession("session-a", makeEntry("alpha fact"));
211
+ await storage.saveBufferSession("session-b", makeEntry("beta fact"));
212
+ const dir = path.join(root, "state", "buffer");
213
+ const names = (await readdir(dir)).filter((n) => n === bufferSessionFileName("session-b"));
214
+ await writeFile(path.join(dir, names[0]), "{not json", "utf8");
215
+
216
+ const state = await storage.loadBuffer();
217
+ assert.equal(state.entries?.["session-a"]?.turns[0]?.content, "alpha fact");
218
+ assert.equal(state.entries?.["session-b"], undefined);
219
+ } finally {
220
+ await rm(root, { recursive: true, force: true });
221
+ }
222
+ });
223
+
224
+ test("a corrupt session file is replaced by the legacy snapshot during migration", async () => {
225
+ const root = await tempRoot("bufio-corrupt-plus-legacy");
226
+ try {
227
+ const stateDir = path.join(root, "state");
228
+ await mkdir(path.join(stateDir, "buffer"), { recursive: true });
229
+ await writeFile(
230
+ path.join(stateDir, "buffer", bufferSessionFileName("session-a")),
231
+ "{not json",
232
+ "utf8",
233
+ );
234
+ const legacy: BufferState = {
235
+ turns: [],
236
+ lastExtractionAt: null,
237
+ extractionCount: 0,
238
+ entries: { "session-a": makeEntry("alpha fact") },
239
+ };
240
+ await writeFile(path.join(stateDir, "buffer.json"), JSON.stringify(legacy), "utf8");
241
+
242
+ const storage = new StorageManager(root);
243
+ const loaded = await storage.loadBuffer();
244
+ assert.equal(loaded.entries?.["session-a"]?.turns[0]?.content, "alpha fact");
245
+ await assert.rejects(stat(path.join(stateDir, "buffer.json")), { code: "ENOENT" });
246
+ } finally {
247
+ await rm(root, { recursive: true, force: true });
248
+ }
249
+ });
250
+
251
+ test("corrupt legacy buffer.json with no session files still throws (reset path preserved)", async () => {
252
+ const root = await tempRoot("bufio-corrupt-legacy");
253
+ try {
254
+ const stateDir = path.join(root, "state");
255
+ await mkdir(stateDir, { recursive: true });
256
+ await writeFile(path.join(stateDir, "buffer.json"), "{not json", "utf8");
257
+ const storage = new StorageManager(root);
258
+ await assert.rejects(() => storage.loadBuffer());
259
+ } finally {
260
+ await rm(root, { recursive: true, force: true });
261
+ }
262
+ });
263
+
264
+ test("per-session save does not run the heavyweight ensureDirectories pass", async () => {
265
+ const root = await tempRoot("bufio-light-save");
266
+ try {
267
+ const storage = new StorageManager(root);
268
+ await storage.saveBufferSession("session-a", makeEntry("alpha fact"));
269
+ // ensureDirectories() materializes facts/, entities/, questions/, ... — a
270
+ // per-session buffer write must only need state/buffer/.
271
+ assert.equal(await exists(path.join(root, "entities")), false, "entities dir created by a buffer session save");
272
+ assert.equal(await exists(path.join(root, "questions")), false, "questions dir created by a buffer session save");
273
+ const state = await storage.loadBuffer();
274
+ assert.equal(state.entries?.["session-a"]?.turns[0]?.content, "alpha fact");
275
+ } finally {
276
+ await rm(root, { recursive: true, force: true });
277
+ }
278
+ });
279
+
280
+ async function exists(p: string): Promise<boolean> {
281
+ try {
282
+ await stat(p);
283
+ return true;
284
+ } catch {
285
+ return false;
286
+ }
287
+ }
288
+
289
+ test("session file names round-trip awkward buffer keys", async () => {
290
+ const root = await tempRoot("bufio-awkward-keys");
291
+ try {
292
+ const storage = new StorageManager(root);
293
+ const keys = ["default", "agent:main:abc123", "weird /slash/key", "__proto__", "unicode-ключ-🔑"];
294
+ for (const [i, key] of keys.entries()) {
295
+ await storage.saveBufferSession(key, makeEntry(`fact-${i}`));
296
+ }
297
+ const state = await storage.loadBuffer();
298
+ for (const [i, key] of keys.entries()) {
299
+ assert.equal(state.entries?.[key]?.turns[0]?.content, `fact-${i}`, `key ${key} did not round-trip`);
300
+ }
301
+ const names = await readdir(path.join(root, "state", "buffer"));
302
+ assert.equal(names.filter((n) => n.endsWith(".json")).length, keys.length, "two keys collided into one file");
303
+ } finally {
304
+ await rm(root, { recursive: true, force: true });
305
+ }
306
+ });
307
+
308
+ test("session file names do not collide on case-insensitive filesystems", () => {
309
+ const keys = ["Session-A", "session-a", "SESSION-A", "h" + "0".repeat(64), "x".repeat(400), "x".repeat(401)];
310
+ const folded = new Set(keys.map((key) => bufferSessionFileName(key).toLowerCase()));
311
+ assert.equal(folded.size, keys.length, "two keys fold to the same file name");
312
+ });
313
+
314
+ test("a symlinked buffer directory is refused and nothing is written through it", async () => {
315
+ const root = await tempRoot("bufio-symlink-dir");
316
+ const outside = await tempRoot("bufio-outside");
317
+ try {
318
+ const storage = new StorageManager(root);
319
+ await mkdir(path.join(root, "state"), { recursive: true });
320
+ await symlink(outside, path.join(root, "state", "buffer"));
321
+
322
+ await assert.rejects(storage.saveBufferSession("session-a", makeEntry("secret")), /symlink/);
323
+ await assert.rejects(storage.deleteBufferSession("session-a"), /symlink/);
324
+ await assert.rejects(storage.loadBuffer(), /symlink/);
325
+ assert.deepEqual(await readdir(outside), [], "nothing may be written outside the memory root");
326
+ } finally {
327
+ await rm(root, { recursive: true, force: true });
328
+ await rm(outside, { recursive: true, force: true });
329
+ }
330
+ });
331
+
332
+ test("a symlinked session file is never followed when loading", async () => {
333
+ const root = await tempRoot("bufio-symlink-file");
334
+ const outside = await tempRoot("bufio-outside-file");
335
+ try {
336
+ const storage = new StorageManager(root);
337
+ await storage.saveBufferSession("session-a", makeEntry("alpha fact"));
338
+ const target = path.join(outside, "stolen.json");
339
+ await writeFile(target, JSON.stringify({ key: "session-evil", ...makeEntry("evil fact") }), "utf8");
340
+ await symlink(target, path.join(root, "state", "buffer", "s-planted.json"));
341
+
342
+ const state = await storage.loadBuffer();
343
+
344
+ assert.equal(state.entries?.["session-a"]?.turns[0]?.content, "alpha fact");
345
+ assert.equal(state.entries?.["session-evil"], undefined, "the symlink target must not be read");
346
+ } finally {
347
+ await rm(root, { recursive: true, force: true });
348
+ await rm(outside, { recursive: true, force: true });
349
+ }
350
+ });
351
+
352
+ test("loading with the wrong secure-store key rejects instead of reading an empty buffer", async () => {
353
+ const root = await tempRoot("bufio-wrong-key");
354
+ try {
355
+ const writer = new StorageManager(root);
356
+ writer.setSecureStoreKey(Buffer.alloc(32, 1), true);
357
+ await writer.saveBufferSession("session-a", makeEntry("sealed turn"));
358
+
359
+ const reader = new StorageManager(root);
360
+ reader.setSecureStoreKey(Buffer.alloc(32, 2), true);
361
+
362
+ await assert.rejects(reader.loadBuffer(), SecureStoreDecryptError);
363
+ // The encrypted file must survive: a rejected load must not let later saves replace it.
364
+ const names = await readdir(path.join(root, "state", "buffer"));
365
+ assert.deepEqual(names, [bufferSessionFileName("session-a")]);
366
+ } finally {
367
+ await rm(root, { recursive: true, force: true });
368
+ }
369
+ });
package/src/storage.ts CHANGED
@@ -142,9 +142,11 @@ import {
142
142
  type TombstoneStats,
143
143
  } from "./lifecycle/tombstones.js";
144
144
  import { supersessionKeysForFact } from "./temporal-supersession.js";
145
+ import { BufferSessionFiles } from "./storage/buffer-session-files.js";
145
146
  import { runSupersessionSideEffects, type SupersessionAuditOptions } from "./storage/supersession-side-effects.js";
146
147
  import type {
147
148
  AccessTrackingEntry,
149
+ BufferEntryState,
148
150
  BufferState,
149
151
  ConfidenceTier,
150
152
  ContinuityIncidentCloseInput,
@@ -5508,23 +5510,16 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
5508
5510
  return deleted;
5509
5511
  }
5510
5512
 
5511
- async loadBuffer(): Promise<BufferState> {
5512
- const bufferPath = path.join(this.stateDir, "buffer.json");
5513
- try {
5514
- const raw = await this.readStorageSecureFile(bufferPath);
5515
- return JSON.parse(raw) as BufferState;
5516
- } catch (err) {
5517
- if (err instanceof SecureStoreLockedError) throw err;
5518
- if (!isErrnoCode(err, "ENOENT")) throw err;
5519
- return { turns: [], lastExtractionAt: null, extractionCount: 0 };
5520
- }
5521
- }
5522
-
5523
- async saveBuffer(state: BufferState): Promise<void> {
5524
- await this.ensureDirectories();
5525
- const bufferPath = path.join(this.stateDir, "buffer.json");
5526
- await this.writeStorageSecureFile(bufferPath, JSON.stringify(state, null, 2));
5513
+ // Buffer persistence: one file per session (storage/buffer-session-files.ts).
5514
+ private bufferFiles?: BufferSessionFiles;
5515
+ private bufferSessionFiles(): BufferSessionFiles {
5516
+ const io = { read: (p: string) => this.readStorageSecureFile(p), write: (p: string, c: string) => this.writeStorageSecureFile(p, c) };
5517
+ return (this.bufferFiles ??= new BufferSessionFiles(this.stateDir, io));
5527
5518
  }
5519
+ async loadBuffer(): Promise<BufferState> { return this.bufferSessionFiles().load(); }
5520
+ async saveBuffer(state: BufferState): Promise<void> { await this.bufferSessionFiles().reconcile(state); }
5521
+ async saveBufferSession(key: string, entry: BufferEntryState): Promise<void> { await this.bufferSessionFiles().save(key, entry); }
5522
+ async deleteBufferSession(key: string): Promise<void> { await this.bufferSessionFiles().remove(key); }
5528
5523
 
5529
5524
  async loadMeta(): Promise<MetaState> {
5530
5525
  const metaPath = path.join(this.stateDir, "meta.json");
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/local-llm-transport.ts"],"sourcesContent":["import { Agent, getGlobalDispatcher } from \"undici\";\n\nimport { log } from \"./logger.js\";\n\n/**\n * Wire transport for local LLM chat completions.\n *\n * Split out of `local-llm.ts` so the client module keeps prompt and\n * response semantics while connection budgets live in one place.\n */\n\nexport interface ChatCompletionRequest {\n url: string;\n headers: Record<string, string>;\n body: string;\n signal: AbortSignal;\n /**\n * Ceiling for this endpoint, i.e. `localLlmTimeoutMs`. Sizes the\n * connection's inactivity budgets. Per-request budgets below the ceiling\n * stay enforced by `signal`.\n */\n budgetMs: number;\n}\n\n/**\n * Is this undici's stock pooling `Agent` — the transport we may widen?\n *\n * `instanceof` alone is not enough: Node's global fetch is backed by its\n * OWN bundled copy of undici, so the dispatcher parked on the well-known\n * global symbol is an `Agent` from a different copy than the one we import,\n * and cross-copy `instanceof` is false. Fall back to the constructor name,\n * which is stable across copies and still distinguishes `ProxyAgent` and\n * `MockAgent` from the stock pool.\n */\nfunction isPlainPoolingAgent(dispatcher: unknown): boolean {\n if (dispatcher instanceof Agent) return true;\n if (!dispatcher || typeof dispatcher !== \"object\") return false;\n const ctor = (dispatcher as { constructor?: unknown }).constructor;\n if (!ctor || typeof ctor !== \"function\") return false;\n return ctor.name === \"Agent\";\n}\n\n/**\n * Connection pool whose header/body inactivity budgets track the configured\n * request budget.\n *\n * Node's global fetch runs on undici, which applies a default\n * `headersTimeout` and `bodyTimeout` of 300s. A non-streaming completion\n * sends its response headers only once generation finishes, so any\n * completion slower than 300s died with a bare `fetch failed` no matter how\n * high `localLlmTimeoutMs` was set — the configured budget was dead letter\n * past five minutes (issue #2148).\n *\n * One pool per client is enough: it is keyed on the ceiling and rebuilt when\n * that changes.\n */\nexport class ChatTransport {\n private agent: Agent | null = null;\n private agentBudgetMs: number | null = null;\n private warnedAboutCustomDispatcher = false;\n\n /**\n * The widened pool, or `undefined` when the process installed its own\n * transport that we must not displace.\n *\n * A deployment can route the local endpoint through a process-wide\n * dispatcher — a `ProxyAgent` for an HTTP proxy, or a custom\n * connect/TLS/DNS dispatcher. Swapping in our own pool would bypass the\n * proxy entirely and break the request, so those are left alone. A\n * `MockAgent` is excluded by the same check, so test transports keep\n * working. The trade-off is explicit: on those setups the 300s cap stands,\n * because only the dispatcher's owner knows how to rebuild it.\n */\n dispatcherFor(budgetMs: number): Agent | undefined {\n if (!isPlainPoolingAgent(getGlobalDispatcher())) {\n if (!this.warnedAboutCustomDispatcher) {\n this.warnedAboutCustomDispatcher = true;\n log.debug(\n \"local LLM: a custom global dispatcher is installed; leaving it in place. \" +\n \"localLlmTimeoutMs above that dispatcher's headersTimeout will not take effect.\",\n );\n }\n return undefined;\n }\n const normalized = Number.isFinite(budgetMs) ? Math.max(0, budgetMs) : 0;\n if (this.agent && this.agentBudgetMs === normalized) return this.agent;\n void this.agent?.close().catch(() => {\n /* pool teardown is best-effort */\n });\n this.agent = new Agent({\n headersTimeout: normalized,\n bodyTimeout: normalized,\n });\n this.agentBudgetMs = normalized;\n return this.agent;\n }\n\n async post(request: ChatCompletionRequest): Promise<Response> {\n const init: RequestInit & { dispatcher?: Agent } = {\n method: \"POST\",\n headers: request.headers,\n body: request.body,\n signal: request.signal,\n };\n const dispatcher = this.dispatcherFor(request.budgetMs);\n if (dispatcher) init.dispatcher = dispatcher;\n return await fetch(request.url, init);\n }\n}\n"],"mappings":";;;;;AAAA,SAAS,OAAO,2BAA2B;AAkC3C,SAAS,oBAAoB,YAA8B;AACzD,MAAI,sBAAsB,MAAO,QAAO;AACxC,MAAI,CAAC,cAAc,OAAO,eAAe,SAAU,QAAO;AAC1D,QAAM,OAAQ,WAAyC;AACvD,MAAI,CAAC,QAAQ,OAAO,SAAS,WAAY,QAAO;AAChD,SAAO,KAAK,SAAS;AACvB;AAgBO,IAAM,gBAAN,MAAoB;AAAA,EACjB,QAAsB;AAAA,EACtB,gBAA+B;AAAA,EAC/B,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EActC,cAAc,UAAqC;AACjD,QAAI,CAAC,oBAAoB,oBAAoB,CAAC,GAAG;AAC/C,UAAI,CAAC,KAAK,6BAA6B;AACrC,aAAK,8BAA8B;AACnC,YAAI;AAAA,UACF;AAAA,QAEF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,UAAM,aAAa,OAAO,SAAS,QAAQ,IAAI,KAAK,IAAI,GAAG,QAAQ,IAAI;AACvE,QAAI,KAAK,SAAS,KAAK,kBAAkB,WAAY,QAAO,KAAK;AACjE,SAAK,KAAK,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,IAErC,CAAC;AACD,SAAK,QAAQ,IAAI,MAAM;AAAA,MACrB,gBAAgB;AAAA,MAChB,aAAa;AAAA,IACf,CAAC;AACD,SAAK,gBAAgB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,KAAK,SAAmD;AAC5D,UAAM,OAA6C;AAAA,MACjD,QAAQ;AAAA,MACR,SAAS,QAAQ;AAAA,MACjB,MAAM,QAAQ;AAAA,MACd,QAAQ,QAAQ;AAAA,IAClB;AACA,UAAM,aAAa,KAAK,cAAc,QAAQ,QAAQ;AACtD,QAAI,WAAY,MAAK,aAAa;AAClC,WAAO,MAAM,MAAM,QAAQ,KAAK,IAAI;AAAA,EACtC;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/local-llm-helpers.ts"],"sourcesContent":["import type { ResponseFormatJSONSchema } from \"openai/resources/shared\";\nimport { log } from \"./logger.js\";\nimport type { LocalLlmRequestPriority } from \"./local-llm.js\";\n\nexport interface LocalLlmChatCompletionOptions {\n temperature?: number;\n maxTokens?: number;\n responseFormat?: { type: string } | ResponseFormatJSONSchema;\n timeoutMs?: number;\n operation?: string;\n forceDisableThinking?: boolean;\n disableThinking?: boolean;\n priority?: LocalLlmRequestPriority;\n signal?: AbortSignal;\n redactProviderErrors?: boolean;\n /** Per-request model; defaults to config.localLlmModel. */\n model?: string;\n /**\n * Optional out-box for the terminal failure cause (issue #2891): on a\n * failed request the client records the last provider error here so\n * callers can classify it (e.g. HTTP 429 → rate-limited) in memory\n * instead of seeing only null. Never logged.\n */\n failureDiag?: { lastError?: unknown };\n}\n\nexport interface LocalLlmChatCompletionResult {\n content: string;\n usage?: { promptTokens: number; completionTokens: number; totalTokens: number };\n}\nexport function isAbortError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const maybe = err as { name?: string; message?: string };\n return (\n maybe.name === \"AbortError\" ||\n maybe.message === \"This operation was aborted\" ||\n maybe.message === \"The operation was aborted\"\n );\n}\n\nexport function waitForRetryBackoff(backoffMs: number, signal?: AbortSignal): Promise<boolean> {\n return new Promise((resolve) => {\n let settled = false;\n let timer: NodeJS.Timeout | undefined;\n const onAbort = (): void => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n signal?.removeEventListener(\"abort\", onAbort);\n resolve(false);\n };\n const onTimer = (): void => {\n if (settled) return;\n settled = true;\n signal?.removeEventListener(\"abort\", onAbort);\n resolve(true);\n };\n timer = setTimeout(onTimer, backoffMs);\n if (signal) {\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n}\n\nexport function normalizeBackendTripReason(reason: string): string {\n const cleaned = reason.replace(/\\s+/g, \" \").replace(/^[-:–—\\s]+/, \"\").trim();\n if (!cleaned) return \"unknown local backend failure\";\n return cleaned.length > 160 ? `${cleaned.slice(0, 157)}...` : cleaned;\n}\n\nexport function extractNonRecoverableBackendReason(reason: string): string | null {\n const match = reason.match(\n /Failed to load model|Library not loaded|different Team IDs|code signature|llm_engine_mlx_amphibian/i,\n );\n return match?.[0] ?? null;\n}\n\nexport function extractNonRecoverableBackendReasonFromErrorText(errorText: string): string | null {\n const directReason = extractNonRecoverableBackendReason(errorText);\n if (directReason) return directReason;\n try {\n const parsed = JSON.parse(errorText) as { error?: { message?: string } };\n return extractNonRecoverableBackendReason(parsed?.error?.message ?? \"\");\n } catch {\n return null;\n }\n}\n\n/**\n * Flatten a fetch rejection into one line, including the `cause` undici hides\n * the real reason behind (`fetch failed` alone says nothing).\n */\nexport function describeFetchFailure(err: unknown): string {\n const name = err instanceof Error ? err.name : \"\";\n const message = err instanceof Error ? err.message : String(err);\n const cause: unknown = err instanceof Error ? (err as Error & { cause?: unknown }).cause : undefined;\n const causeText = cause instanceof Error\n ? ` (cause: ${cause.name}: ${cause.message}${\n typeof (cause as Error & { code?: unknown }).code === \"string\"\n ? ` [${String((cause as Error & { code?: unknown }).code)}]`\n : \"\"\n })`\n : cause === undefined\n ? \"\"\n : ` (cause: ${String(cause)})`;\n return `${name ? `${name}: ` : \"\"}${message}${causeText}`;\n}\n\n/**\n * What to record when every availability probe failed.\n *\n * A probe that TIMED OUT is not evidence the backend is down: the budget is a\n * fixed 2s and a busy event loop can burn it before the socket is scheduled,\n * so the backend looks dead while it answers other callers in single-digit ms.\n * Caching that verdict took extraction offline for a whole health-check\n * interval at a time (issue #2210), so a timeout leaves availability UNKNOWN —\n * this call fails, the next one re-probes instead of reading a stale false.\n *\n * A backend that ANSWERED and said no is a real verdict and stays cached, or\n * the daemon re-probes a known-bad endpoint on every request.\n */\nexport function resolveUnavailableVerdict(args: {\n baseUrl: string;\n sawAbortedProbe: boolean;\n wasAvailable: boolean | null;\n}): { cacheVerdict: boolean; warning: string | null } {\n if (args.sawAbortedProbe) {\n return {\n cacheVerdict: false,\n warning:\n `local LLM availability probe timed out at ${args.baseUrl} (event loop busy?); treating availability as ` +\n \"unknown and re-probing on the next request rather than marking the backend down\",\n };\n }\n return {\n cacheVerdict: true,\n // Surfaced at warn, not debug: extraction stops silently when this flips,\n // and the daemon emits no debug at all unless configured for it.\n warning: args.wasAvailable !== false ? `local LLM became unavailable at ${args.baseUrl}` : null,\n };\n}\n\nexport interface ProbeFetchResult {\n ok: boolean;\n data: unknown;\n status: number | null;\n /** The probe hit its own budget rather than getting an answer. */\n aborted?: boolean;\n}\n\n/**\n * One health-probe request.\n *\n * Every failure is logged with its cause: a transport error and a 404 both\n * returned `ok: false` with nothing recorded, so an unreachable backend was\n * indistinguishable from a wrong path and an availability failure had no\n * diagnosable reason at all (§22, issue #2210).\n */\nexport async function probeFetch(\n url: string,\n options: { timeoutMs: number; headers: Record<string, string>; signal?: AbortSignal },\n): Promise<ProbeFetchResult> {\n const controller = new AbortController();\n const requestSignal = options.signal\n ? AbortSignal.any([options.signal, controller.signal])\n : controller.signal;\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs);\n try {\n const response = await fetch(url, { signal: requestSignal, headers: options.headers });\n clearTimeout(timeout);\n if (!response.ok) {\n log.debug(`local LLM probe: ${url} returned HTTP ${response.status}`);\n return { ok: false, data: null, status: response.status };\n }\n const contentType = response.headers.get(\"content-type\");\n const data = contentType?.includes(\"application/json\") ? await response.json() : await response.text();\n return { ok: true, data, status: response.status };\n } catch (err) {\n clearTimeout(timeout);\n const aborted = isAbortError(err);\n log.debug(\n `local LLM probe: ${url} ${aborted ? `timed out after ${options.timeoutMs}ms` : \"failed\"}: ${describeFetchFailure(err)}`,\n );\n return { ok: false, data: null, status: null, aborted };\n }\n}\n\ninterface PendingProbe {\n promise: Promise<boolean>;\n controller: AbortController;\n /** Callers still awaiting this sequence; at zero the sequence is aborted. */\n waiters: number;\n}\n\n/**\n * Collapses concurrent probe sequences onto one in-flight run.\n *\n * A timed-out availability probe deliberately caches nothing (issue #2210),\n * which removed the false verdict that used to absorb concurrent callers:\n * every queued request would then run its own full probe sequence, on a host\n * already slow enough to blow the probe budget.\n *\n * Cancellation is refcounted rather than shared: one caller walking away must\n * not abort a sequence the others are still waiting on, so the underlying task\n * is aborted only once every waiter has gone.\n */\nexport class SingleFlightProbe {\n private pending: PendingProbe | null = null;\n\n run(task: (signal: AbortSignal) => Promise<boolean>, signal?: AbortSignal): Promise<boolean> {\n let active = this.pending;\n if (!active) {\n const controller = new AbortController();\n const created: PendingProbe = { promise: Promise.resolve(false), controller, waiters: 0 };\n created.promise = task(controller.signal).finally(() => {\n if (this.pending === created) this.pending = null;\n });\n this.pending = created;\n active = created;\n }\n const entry = active;\n entry.waiters += 1;\n let released = false;\n const release = () => {\n if (released) return;\n released = true;\n entry.waiters -= 1;\n if (entry.waiters > 0) return;\n // Detach BEFORE aborting. `pending` would otherwise still point at this\n // doomed entry until the task's own `finally` runs a microtask later, and\n // a caller arriving in that window would join an aborted sequence and\n // read its `false` as a verdict.\n if (this.pending === entry) this.pending = null;\n entry.controller.abort();\n };\n if (!signal) return entry.promise.finally(release);\n return new Promise<boolean>((resolve, reject) => {\n const onAbort = () => {\n release();\n resolve(false);\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n entry.promise.then(\n (value) => {\n signal.removeEventListener(\"abort\", onAbort);\n release();\n resolve(value);\n },\n (err: unknown) => {\n signal.removeEventListener(\"abort\", onAbort);\n release();\n reject(err instanceof Error ? err : new Error(String(err)));\n },\n );\n });\n }\n}\n"],"mappings":"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","names":[]}