@remnic/core 9.3.613 → 9.3.614

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 (376) hide show
  1. package/dist/access-cli.js +58 -57
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +4 -2
  4. package/dist/access-http.js +22 -22
  5. package/dist/access-mcp.d.ts +9 -2
  6. package/dist/access-mcp.js +19 -19
  7. package/dist/access-schema.d.ts +12 -12
  8. package/dist/access-schema.js +3 -3
  9. package/dist/{access-service-D2J9dh_9.d.ts → access-service-DGG_2xPK.d.ts} +1 -1
  10. package/dist/access-service.d.ts +2 -2
  11. package/dist/access-service.js +16 -16
  12. package/dist/active-recall.js +20 -3
  13. package/dist/active-recall.js.map +1 -1
  14. package/dist/adapters/index.js +4 -4
  15. package/dist/adapters/registry.js +2 -2
  16. package/dist/behavior-learner.js +2 -3
  17. package/dist/behavior-learner.js.map +1 -1
  18. package/dist/bootstrap.d.ts +1 -1
  19. package/dist/briefing.js +3 -3
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/buffer.js +1 -1
  22. package/dist/calibration.d.ts +5 -2
  23. package/dist/calibration.js +7 -5
  24. package/dist/calibration.js.map +1 -1
  25. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  26. package/dist/causal-consolidation.d.ts +8 -2
  27. package/dist/causal-consolidation.js +13 -11
  28. package/dist/causal-consolidation.js.map +1 -1
  29. package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
  30. package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
  31. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  32. package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
  33. package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
  34. package/dist/chunk-2SGJY2UY.js.map +1 -0
  35. package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
  36. package/dist/chunk-3MAONBX3.js.map +1 -0
  37. package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
  38. package/dist/chunk-3PY7VHV7.js.map +1 -0
  39. package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
  40. package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
  41. package/dist/chunk-3T74IZB3.js.map +1 -0
  42. package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
  43. package/dist/chunk-4HFJQCJZ.js.map +1 -0
  44. package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
  45. package/dist/chunk-4R4KTDIE.js.map +1 -0
  46. package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
  47. package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
  48. package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
  49. package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
  50. package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
  51. package/dist/chunk-6L46YAEZ.js.map +1 -0
  52. package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
  53. package/dist/{chunk-IOTENEVL.js → chunk-7YQFWOF7.js} +57 -50
  54. package/dist/chunk-7YQFWOF7.js.map +1 -0
  55. package/dist/{chunk-2QANQKSQ.js → chunk-ADNZVFXG.js} +15 -15
  56. package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
  57. package/dist/chunk-AL4RAJL5.js.map +1 -0
  58. package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
  59. package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
  60. package/dist/{chunk-TH67Q46T.js → chunk-B6FDZPCF.js} +17 -9
  61. package/dist/chunk-B6FDZPCF.js.map +1 -0
  62. package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
  63. package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
  64. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  65. package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
  66. package/dist/chunk-C6C7XVKG.js.map +1 -0
  67. package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
  68. package/dist/chunk-CI7RKSRE.js.map +1 -0
  69. package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
  70. package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
  71. package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
  72. package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
  73. package/dist/chunk-DCGT4FPP.js.map +1 -0
  74. package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
  75. package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
  76. package/dist/chunk-DEVUWMME.js.map +1 -0
  77. package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
  78. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  79. package/dist/chunk-FAV25DUZ.js +12 -0
  80. package/dist/chunk-FAV25DUZ.js.map +1 -0
  81. package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
  82. package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
  83. package/dist/chunk-GDB4J2H3.js.map +1 -0
  84. package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
  85. package/dist/chunk-GLPBYIXN.js.map +1 -0
  86. package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
  87. package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
  88. package/dist/chunk-IBTZEBUD.js.map +1 -0
  89. package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
  90. package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
  91. package/dist/chunk-JOASJWQR.js.map +1 -0
  92. package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
  93. package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
  94. package/dist/chunk-KBL3JJR6.js.map +1 -0
  95. package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
  96. package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
  97. package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
  98. package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
  99. package/dist/chunk-MHQC2WU2.js.map +1 -0
  100. package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
  101. package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
  102. package/dist/chunk-ODPLEWB6.js.map +1 -0
  103. package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
  104. package/dist/chunk-OIF36KGD.js.map +1 -0
  105. package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
  106. package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
  107. package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
  108. package/dist/chunk-PYIFUBRK.js.map +1 -0
  109. package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
  110. package/dist/{chunk-PPPZY2EU.js → chunk-QEMCQFDW.js} +2 -2
  111. package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
  112. package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
  113. package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
  114. package/dist/chunk-RG3LBSGH.js.map +1 -0
  115. package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
  116. package/dist/chunk-S53OYO3F.js.map +1 -0
  117. package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
  118. package/dist/chunk-SCPFRKIT.js.map +1 -0
  119. package/dist/{chunk-HJNQQICM.js → chunk-T5XWMMU2.js} +107 -50
  120. package/dist/chunk-T5XWMMU2.js.map +1 -0
  121. package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
  122. package/dist/chunk-T7N6KQGS.js.map +1 -0
  123. package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
  124. package/dist/chunk-TNOWU6RP.js.map +1 -0
  125. package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
  126. package/dist/chunk-TZVQQTG4.js.map +1 -0
  127. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  128. package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
  129. package/dist/{chunk-5RPTH6AU.js → chunk-VPGUMLBA.js} +8 -7
  130. package/dist/chunk-VPGUMLBA.js.map +1 -0
  131. package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
  132. package/dist/chunk-WB3LYXC5.js.map +1 -0
  133. package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
  134. package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
  135. package/dist/chunk-X6IRLNOO.js.map +1 -0
  136. package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
  137. package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
  138. package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
  139. package/dist/chunk-YRMKDTKF.js.map +1 -0
  140. package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
  141. package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
  142. package/dist/{cli-OrfKXNU4.d.ts → cli-DWeu7eTY.d.ts} +6 -2
  143. package/dist/cli.d.ts +3 -3
  144. package/dist/cli.js +60 -59
  145. package/dist/compounding/engine.js +3 -3
  146. package/dist/compounding/preference-consolidator.js +39 -11
  147. package/dist/compounding/preference-consolidator.js.map +1 -1
  148. package/dist/config.js +1 -1
  149. package/dist/connectors/codex-materialize-runner.js +3 -3
  150. package/dist/connectors/index.js +3 -3
  151. package/dist/consolidation-provenance-check.js +1 -1
  152. package/dist/contradiction/index.js +4 -4
  153. package/dist/conversation-index/backend.js +2 -2
  154. package/dist/conversation-index/indexer.js +1 -1
  155. package/dist/cross-namespace-budget.js +1 -1
  156. package/dist/enrichment/index.js +1 -1
  157. package/dist/entity-retrieval.js +3 -3
  158. package/dist/evals.js +1 -1
  159. package/dist/explicit-capture.d.ts +1 -1
  160. package/dist/extraction-judge.js +8 -1
  161. package/dist/extraction.js +2 -2
  162. package/dist/fallback-llm.d.ts +23 -6
  163. package/dist/fallback-llm.js +5 -3
  164. package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
  165. package/dist/index.d.ts +3 -3
  166. package/dist/index.js +94 -93
  167. package/dist/index.js.map +1 -1
  168. package/dist/lcm/archive.js +2 -2
  169. package/dist/lcm/engine.js +5 -5
  170. package/dist/lcm/index.js +7 -7
  171. package/dist/lcm/summarizer.js +3 -3
  172. package/dist/maintenance/memory-governance-cron.d.ts +6 -4
  173. package/dist/maintenance/memory-governance-cron.js +1 -1
  174. package/dist/maintenance/memory-governance.js +3 -3
  175. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  176. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  177. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  178. package/dist/mcp-memory-inspector-app.js +1 -1
  179. package/dist/migrate/from-engram.js +1 -1
  180. package/dist/namespaces/migrate.js +16 -15
  181. package/dist/namespaces/search.js +12 -11
  182. package/dist/namespaces/storage.js +3 -3
  183. package/dist/network/webdav.d.ts +2 -0
  184. package/dist/network/webdav.js +1 -1
  185. package/dist/objective-state-writers.js +2 -2
  186. package/dist/operator-toolkit.d.ts +3 -1
  187. package/dist/operator-toolkit.js +21 -20
  188. package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
  189. package/dist/orchestrator.d.ts +1 -1
  190. package/dist/orchestrator.js +47 -44
  191. package/dist/patterns-cli.js +1 -1
  192. package/dist/qmd-recall-cache.d.ts +2 -0
  193. package/dist/qmd-recall-cache.js +1 -1
  194. package/dist/qmd.d.ts +37 -2
  195. package/dist/qmd.js +4 -1
  196. package/dist/recall-explain-renderer.js +3 -3
  197. package/dist/recall-planner-llm.d.ts +57 -0
  198. package/dist/recall-planner-llm.js +167 -0
  199. package/dist/recall-planner-llm.js.map +1 -0
  200. package/dist/recall-xray-cli.js +4 -4
  201. package/dist/recall-xray-renderer.js +3 -3
  202. package/dist/recall-xray.js +2 -2
  203. package/dist/resume-bundles.js +2 -2
  204. package/dist/retrieval-agents.js +2 -2
  205. package/dist/routing/store.js +1 -1
  206. package/dist/schemas.d.ts +22 -22
  207. package/dist/search/factory.js +11 -10
  208. package/dist/search/index.js +11 -10
  209. package/dist/search/lancedb-backend.d.ts +1 -1
  210. package/dist/search/lancedb-backend.js +3 -2
  211. package/dist/search/meilisearch-backend.d.ts +1 -1
  212. package/dist/search/meilisearch-backend.js +3 -2
  213. package/dist/search/noop-backend.d.ts +1 -1
  214. package/dist/search/noop-backend.js +1 -1
  215. package/dist/search/orama-backend.d.ts +1 -1
  216. package/dist/search/orama-backend.js +3 -2
  217. package/dist/search/port.d.ts +6 -1
  218. package/dist/search/port.js +7 -0
  219. package/dist/search/remote-backend.d.ts +1 -1
  220. package/dist/search/remote-backend.js +1 -1
  221. package/dist/semantic-consolidation.js +4 -4
  222. package/dist/semantic-rule-promotion.js +3 -3
  223. package/dist/semantic-rule-verifier.js +3 -3
  224. package/dist/session-observer-state.js +1 -1
  225. package/dist/storage.js +2 -2
  226. package/dist/summarizer.js +2 -2
  227. package/dist/temporal-index.js +1 -1
  228. package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
  229. package/dist/transfer/backup.js +2 -2
  230. package/dist/transfer/capsule-export.js +2 -2
  231. package/dist/transfer/capsule-import.js +2 -2
  232. package/dist/transfer/export-sqlite.js +1 -1
  233. package/dist/transfer/types.d.ts +12 -12
  234. package/dist/types.d.ts +32 -0
  235. package/dist/types.js +1 -1
  236. package/dist/utility-learner.js +1 -1
  237. package/dist/utility-runtime.js +2 -2
  238. package/dist/verified-recall.js +3 -3
  239. package/dist/work/board.js +2 -2
  240. package/dist/work/storage.d.ts +2 -0
  241. package/dist/work/storage.js +1 -1
  242. package/package.json +1 -1
  243. package/src/access-http.ts +3 -0
  244. package/src/access-mcp.test.ts +51 -0
  245. package/src/access-mcp.ts +26 -5
  246. package/src/active-recall.test.ts +40 -0
  247. package/src/active-recall.ts +19 -2
  248. package/src/behavior-learner.ts +5 -3
  249. package/src/buffer-session.test.ts +58 -0
  250. package/src/buffer-surprise-trigger.test.ts +4 -18
  251. package/src/buffer.ts +39 -11
  252. package/src/calibration.ts +10 -4
  253. package/src/causal-consolidation.test.ts +47 -2
  254. package/src/causal-consolidation.ts +13 -9
  255. package/src/cli.ts +19 -4
  256. package/src/compounding/engine.ts +2 -0
  257. package/src/compounding/preference-consolidator.test.ts +292 -0
  258. package/src/compounding/preference-consolidator.ts +55 -19
  259. package/src/config.test.ts +213 -0
  260. package/src/config.ts +175 -4
  261. package/src/connectors/codex-materialize-runner.ts +7 -4
  262. package/src/consolidation-provenance-check.ts +24 -5
  263. package/src/conversation-index/indexer.test.ts +22 -0
  264. package/src/conversation-index/indexer.ts +7 -3
  265. package/src/cross-namespace-budget.test.ts +44 -21
  266. package/src/cross-namespace-budget.ts +2 -2
  267. package/src/enrichment/pipeline.ts +11 -16
  268. package/src/evals.ts +1 -1
  269. package/src/extraction-judge-chain.test.ts +55 -0
  270. package/src/extraction-judge.ts +7 -9
  271. package/src/extraction.ts +16 -5
  272. package/src/fallback-llm.test.ts +600 -1
  273. package/src/fallback-llm.ts +91 -22
  274. package/src/maintenance/memory-governance-cron.ts +39 -29
  275. package/src/mcp-memory-inspector-app.ts +54 -12
  276. package/src/message-parts/index.ts +6 -0
  277. package/src/message-parts/message-parts.test.ts +30 -0
  278. package/src/migrate/from-engram.ts +19 -5
  279. package/src/namespaces/search.test.ts +15 -2
  280. package/src/namespaces/search.ts +1 -1
  281. package/src/network/webdav.ts +61 -21
  282. package/src/operator-toolkit.ts +6 -2
  283. package/src/orchestrator.ts +173 -20
  284. package/src/qmd-client.test.ts +85 -0
  285. package/src/qmd-recall-cache.test.ts +16 -0
  286. package/src/qmd-recall-cache.ts +7 -0
  287. package/src/qmd.test.ts +54 -0
  288. package/src/qmd.ts +119 -19
  289. package/src/recall-planner-llm.test.ts +224 -0
  290. package/src/recall-planner-llm.ts +289 -0
  291. package/src/routing/store.ts +4 -8
  292. package/src/search/factory.ts +3 -0
  293. package/src/search/lancedb-backend.ts +15 -3
  294. package/src/search/meilisearch-backend.ts +70 -7
  295. package/src/search/noop-backend.ts +5 -1
  296. package/src/search/orama-backend.ts +15 -3
  297. package/src/search/port.ts +15 -0
  298. package/src/search/remote-backend.ts +5 -1
  299. package/src/session-observer-state.ts +1 -1
  300. package/src/summarizer.ts +3 -3
  301. package/src/temporal-index.test.ts +18 -0
  302. package/src/temporal-index.ts +45 -0
  303. package/src/training-export/cli-date-validation.test.ts +36 -0
  304. package/src/training-export/date-parse.ts +21 -2
  305. package/src/transfer/export-sqlite.ts +3 -0
  306. package/src/types.ts +35 -0
  307. package/src/utility-learner.ts +1 -0
  308. package/src/work/storage.ts +23 -0
  309. package/dist/chunk-5RPTH6AU.js.map +0 -1
  310. package/dist/chunk-AJA46VX5.js.map +0 -1
  311. package/dist/chunk-C4SQJZAF.js.map +0 -1
  312. package/dist/chunk-CHCA44C3.js.map +0 -1
  313. package/dist/chunk-CSKLPDN6.js.map +0 -1
  314. package/dist/chunk-DLJ4IR6M.js.map +0 -1
  315. package/dist/chunk-EAZGEEG2.js.map +0 -1
  316. package/dist/chunk-EVZFIAPG.js.map +0 -1
  317. package/dist/chunk-G3Z3QEF5.js.map +0 -1
  318. package/dist/chunk-GMAG2HS4.js.map +0 -1
  319. package/dist/chunk-HENLZHIT.js.map +0 -1
  320. package/dist/chunk-HINSGUA7.js.map +0 -1
  321. package/dist/chunk-HJNQQICM.js.map +0 -1
  322. package/dist/chunk-HPWVAEET.js.map +0 -1
  323. package/dist/chunk-IOTENEVL.js.map +0 -1
  324. package/dist/chunk-IP73YCZP.js.map +0 -1
  325. package/dist/chunk-JHMFYY7L.js.map +0 -1
  326. package/dist/chunk-JNANKJLN.js.map +0 -1
  327. package/dist/chunk-KGK2QKWL.js.map +0 -1
  328. package/dist/chunk-KM2A35EO.js.map +0 -1
  329. package/dist/chunk-KVEVLBKC.js.map +0 -1
  330. package/dist/chunk-L227SKTB.js.map +0 -1
  331. package/dist/chunk-LZ3VEOU5.js.map +0 -1
  332. package/dist/chunk-NOMEVTUD.js.map +0 -1
  333. package/dist/chunk-NZPF2SYV.js.map +0 -1
  334. package/dist/chunk-PCI747N2.js.map +0 -1
  335. package/dist/chunk-TH67Q46T.js.map +0 -1
  336. package/dist/chunk-UWY7GIVS.js.map +0 -1
  337. package/dist/chunk-VJXSUAO7.js.map +0 -1
  338. package/dist/chunk-XKIQZXUB.js.map +0 -1
  339. package/dist/chunk-XPSVGJYA.js.map +0 -1
  340. package/dist/chunk-XSWKORGM.js.map +0 -1
  341. package/dist/chunk-YCN4BVDK.js.map +0 -1
  342. package/dist/chunk-ZDTVJXIP.js.map +0 -1
  343. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  344. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  345. /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
  346. /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
  347. /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
  348. /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
  349. /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
  350. /package/dist/{chunk-2QANQKSQ.js.map → chunk-ADNZVFXG.js.map} +0 -0
  351. /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
  352. /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
  353. /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
  354. /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
  355. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  356. /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
  357. /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
  358. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
  359. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  360. /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
  361. /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
  362. /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
  363. /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
  364. /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
  365. /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
  366. /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
  367. /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
  368. /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
  369. /package/dist/{chunk-PPPZY2EU.js.map → chunk-QEMCQFDW.js.map} +0 -0
  370. /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
  371. /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
  372. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  373. /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
  374. /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
  375. /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
  376. /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
@@ -173,9 +173,10 @@ var SmartBuffer = class {
173
173
  const shouldPromote = surprise > this.config.bufferSurpriseThreshold;
174
174
  let triggered = false;
175
175
  if (shouldPromote) {
176
- const currentTurns = await this.getExtractionTurnsIfTurnSnapshotStillCurrent(
176
+ const currentTurns = await this.getExtractionTurnsIfBufferSnapshotStillCurrent(
177
177
  bufferKey,
178
- mutation.turnSnapshot
178
+ mutation.activeTurnsSnapshot,
179
+ mutation.retainedTurnsSnapshot
179
180
  );
180
181
  if (currentTurns) {
181
182
  log.debug(
@@ -218,7 +219,8 @@ var SmartBuffer = class {
218
219
  const entry = this.entryFor(bufferKey);
219
220
  const priorTurns = entry.turns.slice();
220
221
  entry.turns.push(turn);
221
- const turnSnapshot = copyBufferTurn(turn);
222
+ const activeTurnsSnapshot = entry.turns.map(copyBufferTurn);
223
+ const retainedTurnsSnapshot = (entry.retainedTurns ?? []).map(copyBufferTurn);
222
224
  if (bufferKey === "default") {
223
225
  this.state.turns = entry.turns;
224
226
  }
@@ -231,20 +233,19 @@ var SmartBuffer = class {
231
233
  decision,
232
234
  signalLevel: signal.level,
233
235
  priorTurns,
234
- turnSnapshot,
236
+ activeTurnsSnapshot,
237
+ retainedTurnsSnapshot,
235
238
  turnCountInWindow
236
239
  };
237
240
  }
238
- async getExtractionTurnsIfTurnSnapshotStillCurrent(bufferKey, turnSnapshot) {
241
+ async getExtractionTurnsIfBufferSnapshotStillCurrent(bufferKey, activeTurnsSnapshot, retainedTurnsSnapshot) {
239
242
  return this.enqueueMutation(async () => {
240
243
  await this.loadUnlocked();
241
244
  const entry = this.peekEntry(bufferKey);
242
245
  if (!entry) return null;
243
- const stillCurrent = entry.turns.some(
244
- (turn) => bufferTurnsEqual(turn, turnSnapshot)
245
- );
246
- if (!stillCurrent) return null;
247
246
  const retained = entry.retainedTurns ?? [];
247
+ const stillCurrent = bufferTurnArrayIsSuffixOfSnapshot(entry.turns, activeTurnsSnapshot) && bufferTurnArraysEqual(retained, retainedTurnsSnapshot);
248
+ if (!stillCurrent) return null;
248
249
  return [...retained, ...entry.turns];
249
250
  });
250
251
  }
@@ -538,6 +539,18 @@ function bufferTurnsEqual(left, right) {
538
539
  if (!left) return false;
539
540
  return left.role === right.role && left.content === right.content && left.timestamp === right.timestamp && left.sessionKey === right.sessionKey && left.logicalSessionKey === right.logicalSessionKey && left.providerThreadId === right.providerThreadId && left.turnFingerprint === right.turnFingerprint && left.persistProcessedFingerprint === right.persistProcessedFingerprint;
540
541
  }
542
+ function bufferTurnArraysEqual(left, right) {
543
+ return left.length === right.length && left.every((turn, index) => bufferTurnsEqual(turn, right[index]));
544
+ }
545
+ function bufferTurnArrayIsSuffixOfSnapshot(liveTurns, snapshot) {
546
+ if (liveTurns.length === 0 || liveTurns.length > snapshot.length) {
547
+ return false;
548
+ }
549
+ const offset = snapshot.length - liveTurns.length;
550
+ return liveTurns.every(
551
+ (turn, index) => bufferTurnsEqual(turn, snapshot[offset + index])
552
+ );
553
+ }
541
554
  function liveTurnsFromExtractionSnapshot(entry, extractedTurns) {
542
555
  const retainedTurns = entry.retainedTurns ?? [];
543
556
  if (retainedTurns.length > 0 && extractedTurns.length >= retainedTurns.length && retainedTurns.every(
@@ -593,4 +606,4 @@ function probeWithTimeout(inflight, timeoutMs) {
593
606
  export {
594
607
  SmartBuffer
595
608
  };
596
- //# sourceMappingURL=chunk-EVZFIAPG.js.map
609
+ //# sourceMappingURL=chunk-IBTZEBUD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/buffer.ts"],"sourcesContent":["import { log } from \"./logger.js\";\nimport { scanSignals } from \"./signal.js\";\nimport type { StorageManager } from \"./storage.js\";\nimport type {\n BufferEntryState,\n BufferState,\n BufferSurpriseEvent,\n BufferTurn,\n PluginConfig,\n SignalLevel,\n} from \"./types.js\";\n\nexport type TriggerDecision = \"extract_now\" | \"extract_batch\" | \"keep_buffering\";\n\nexport interface AddTurnOutcome {\n decision: TriggerDecision;\n extractionTurns?: BufferTurn[];\n}\n\n/**\n * Optional surprise probe injected into `SmartBuffer`.\n *\n * Computes a D-MEM-style novelty score in `[0, 1]` for an incoming turn.\n * The buffer treats the probe as purely additive: if it is not provided, if\n * the feature flag is off, or if the probe throws/times out, the buffer\n * falls back to the existing signal/turn-count/time triggers unchanged.\n *\n * Callers are responsible for sampling recent memories and passing them\n * through the embedding pipeline — the buffer does not want to know about\n * storage, embeddings, or QMD.\n *\n * @param bufferKey Identifier for the active buffer (session/thread).\n * @param turn The incoming turn whose novelty is being scored.\n * @param recentTurns Turns already buffered for this key (most recent first\n * is NOT guaranteed — treat as unordered corpus).\n * @returns A surprise score in `[0, 1]`, or `null` if no score could be\n * produced (e.g. empty corpus, probe declined to embed).\n */\nexport interface BufferSurpriseProbe {\n scoreTurn(\n bufferKey: string,\n turn: BufferTurn,\n recentTurns: readonly BufferTurn[],\n ): Promise<number | null>;\n}\n\nconst MAX_BUFFER_ENTRY_COUNT = 200;\n\n/**\n * Minimal data carried on the serialized telemetry write chain\n * (issue #563 PR 3).\n *\n * We intentionally do NOT capture the full `BufferTurn` here: under\n * slow filesystem latency the chain can back up, and retaining\n * `turn.content` for every pending append causes memory pressure on\n * large conversations. Only the fields the ledger row actually needs\n * cross the chain boundary.\n */\ninterface SurpriseTelemetryQueueEntry {\n bufferKey: string;\n turnRole: \"user\" | \"assistant\";\n sessionKey: string | null;\n surpriseScore: number;\n triggered: boolean;\n turnCountInWindow: number;\n /**\n * ISO timestamp captured at the moment the turn was scored, NOT when\n * the ledger append eventually runs. Backpressure on the serialized\n * write chain could otherwise shift event timestamps away from the\n * real decision moment and distort the distribution report (p90\n * inflated, current-threshold row misidentified).\n */\n timestamp: string;\n /**\n * Threshold value in force when `triggered` was computed. Must be\n * snapshot here rather than read from `config` at emit time — a\n * concurrent config change between queue and write would otherwise\n * record `triggered=true` against a newer threshold the operator\n * never set, distorting precision/recall interpretation.\n */\n threshold: number;\n}\n\ninterface AddTurnMutationResult {\n decision: TriggerDecision;\n signalLevel: SignalLevel;\n priorTurns: BufferTurn[];\n activeTurnsSnapshot: BufferTurn[];\n retainedTurnsSnapshot: BufferTurn[];\n turnCountInWindow: number;\n}\n\nexport class SmartBuffer {\n private state: BufferState;\n private loaded = false;\n private loadPromise: Promise<void> | null = null;\n private readonly surpriseProbe: BufferSurpriseProbe | null;\n private mutationChain: Promise<unknown> = Promise.resolve();\n /**\n * Serialized write chain for `BUFFER_SURPRISE` telemetry events.\n *\n * The telemetry path is fire-and-forget (`addTurn` does not await the\n * ledger append), but multiple concurrent appends would still settle\n * out of order under variable filesystem latency. The report path\n * assumes chronological ordering — it slices the tail of the ledger\n * and treats the most recent entry as the current threshold in force.\n * Chaining ensures each append only runs after the previous settles,\n * preserving wall-clock order.\n *\n * We include a `.catch` on every link so a rejected append does not\n * poison the chain (CLAUDE.md rule #40).\n */\n private surpriseTelemetryWriteChain: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly config: PluginConfig,\n private readonly storage: StorageManager,\n surpriseProbe: BufferSurpriseProbe | null = null,\n ) {\n this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };\n this.surpriseProbe = surpriseProbe;\n }\n\n private enqueueMutation<T>(op: () => Promise<T>): Promise<T> {\n const run = this.mutationChain.catch(() => {}).then(op);\n this.mutationChain = run.catch(() => {});\n return run;\n }\n\n private entryFor(key: string): BufferEntryState {\n if (!this.state.entries) {\n this.state.entries = Object.create(null) as NonNullable<BufferState[\"entries\"]>;\n }\n const entries = this.state.entries as NonNullable<BufferState[\"entries\"]>;\n if (Object.hasOwn(entries, key)) {\n const stored = entries[key];\n // Guard against corrupted state/buffer.json — if the stored entry\n // is not a valid object shape, discard it and recreate.\n if (stored && typeof stored === \"object\" && Array.isArray(stored.turns)) {\n return stored;\n }\n // Corrupted — fall through to recreate.\n }\n const created: BufferEntryState = {\n turns: [],\n lastExtractionAt: null,\n extractionCount: 0,\n };\n entries[key] = created;\n return created;\n }\n\n private peekEntry(key: string): BufferEntryState | null {\n const existing = this.state.entries?.[key];\n if (existing) return existing;\n if (key !== \"default\") return null;\n return {\n turns: Array.isArray(this.state.turns) ? this.state.turns : [],\n lastExtractionAt: this.state.lastExtractionAt ?? null,\n extractionCount:\n typeof this.state.extractionCount === \"number\" ? this.state.extractionCount : 0,\n };\n }\n\n private normalizeState(state: BufferState): BufferState {\n const entries = Object.assign(\n Object.create(null),\n state.entries ?? {},\n ) as NonNullable<BufferState[\"entries\"]>;\n if (!entries.default) {\n entries.default = {\n turns: Array.isArray(state.turns) ? [...state.turns] : [],\n lastExtractionAt: state.lastExtractionAt ?? null,\n extractionCount:\n typeof state.extractionCount === \"number\" ? state.extractionCount : 0,\n };\n }\n return {\n turns: entries.default.turns,\n lastExtractionAt: entries.default.lastExtractionAt,\n extractionCount: entries.default.extractionCount,\n entries,\n };\n }\n\n private entryActivityAt(entry: BufferEntryState): number {\n const lastTurnAt = entry.turns.reduce((latest, turn) => {\n const parsed = Date.parse(turn.timestamp);\n return Number.isFinite(parsed) ? Math.max(latest, parsed) : latest;\n }, -1);\n const lastExtractionAt =\n typeof entry.lastExtractionAt === \"string\"\n ? Date.parse(entry.lastExtractionAt)\n : Number.NaN;\n return Math.max(\n lastTurnAt,\n Number.isFinite(lastExtractionAt) ? lastExtractionAt : -1,\n );\n }\n\n private pruneEntries(retainKeys: string[]): void {\n const entries = this.state.entries;\n if (!entries) return;\n const keys = Object.keys(entries);\n if (keys.length <= MAX_BUFFER_ENTRY_COUNT) return;\n\n const insertionOrder = new Map(keys.map((key, index) => [key, index]));\n const removable = keys\n .filter((key) => key !== \"default\" && !retainKeys.includes(key))\n .filter((key) => (entries[key]?.turns.length ?? 0) === 0)\n .sort((left, right) => {\n const leftAt = this.entryActivityAt(entries[left] ?? {\n turns: [],\n lastExtractionAt: null,\n extractionCount: 0,\n });\n const rightAt = this.entryActivityAt(entries[right] ?? {\n turns: [],\n lastExtractionAt: null,\n extractionCount: 0,\n });\n if (leftAt !== rightAt) return leftAt - rightAt;\n return (insertionOrder.get(left) ?? 0) - (insertionOrder.get(right) ?? 0);\n });\n\n const removableCount = Math.max(0, keys.length - MAX_BUFFER_ENTRY_COUNT);\n for (const key of removable.slice(0, removableCount)) {\n delete entries[key];\n }\n }\n\n private async loadUnlocked(): Promise<void> {\n if (this.loaded) return;\n if (!this.loadPromise) {\n this.loadPromise = this.storage.loadBuffer()\n .then((state) => {\n this.state = this.normalizeState(state);\n this.loaded = true;\n })\n .finally(() => {\n this.loadPromise = null;\n });\n }\n await this.loadPromise;\n }\n\n async load(): Promise<void> {\n await this.enqueueMutation(async () => this.loadUnlocked());\n }\n\n /**\n * Reset the buffer to an empty, usable state.\n * Called when the persisted buffer file is corrupt and load() fails,\n * so the buffer can still accept new turns for the rest of the session.\n */\n resetToEmpty(): void {\n this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };\n this.loaded = true;\n }\n\n private async saveUnlocked(): Promise<void> {\n await this.storage.saveBuffer(this.state);\n }\n\n async save(): Promise<void> {\n await this.enqueueMutation(async () => this.saveUnlocked());\n }\n\n async addTurn(bufferKey: string, turn: BufferTurn): Promise<TriggerDecision> {\n return (await this.addTurnWithOutcome(bufferKey, turn)).decision;\n }\n\n async addTurnWithOutcome(\n bufferKey: string,\n turn: BufferTurn,\n ): Promise<AddTurnOutcome> {\n const mutation = await this.enqueueMutation(() => this.recordTurnUnlocked(bufferKey, turn));\n let decision = mutation.decision;\n let extractionTurns: BufferTurn[] | undefined;\n\n // Surprise-gated flush (issue #563). Additive only: if the probe is\n // disabled, unavailable, or the score is below threshold, the decision\n // from the existing trigger logic stands. The probe only ever *promotes*\n // `keep_buffering` → `extract_now`; it never suppresses an existing\n // flush. This preserves the invariant that enabling surprise cannot\n // *reduce* extraction frequency.\n if (\n decision === \"keep_buffering\" &&\n this.config.bufferSurpriseTriggerEnabled &&\n this.surpriseProbe !== null &&\n // Matching the existing \"smart\" branch: surprise is a lower-tier\n // novelty signal that should not second-guess a high-signal hit\n // (which already flushes) or fight `every_n` / `time_based` modes.\n this.config.triggerMode === \"smart\" &&\n mutation.signalLevel !== \"high\"\n ) {\n const surprise = await this.computeSurpriseSafe(bufferKey, turn, mutation.priorTurns);\n if (surprise !== null) {\n const shouldPromote = surprise > this.config.bufferSurpriseThreshold;\n let triggered = false;\n if (shouldPromote) {\n const currentTurns = await this.getExtractionTurnsIfBufferSnapshotStillCurrent(\n bufferKey,\n mutation.activeTurnsSnapshot,\n mutation.retainedTurnsSnapshot,\n );\n if (currentTurns) {\n log.debug(\n `buffer[${bufferKey}]: surprise=${surprise.toFixed(3)} > threshold=${this.config.bufferSurpriseThreshold} → extract_now`,\n );\n decision = \"extract_now\";\n triggered = true;\n extractionTurns = currentTurns;\n } else {\n log.debug(\n `buffer[${bufferKey}]: surprise=${surprise.toFixed(3)} ignored because buffer changed before probe resolved`,\n );\n }\n }\n // Emit telemetry on every scored turn — both triggering and\n // non-triggering — so operators can fit the threshold to real\n // traffic distributions. Fire-and-forget: `addTurn` does NOT\n // await the ledger append, so slow/contended filesystems cannot\n // add JSONL-append latency to every `processTurn`. But we DO\n // serialize writes through a promise chain so concurrent\n // appends settle in wall-clock order — the report path assumes\n // chronological tail rows and reads the most recent as the\n // \"current\" threshold.\n //\n // Project only the fields we need into the queue entry rather\n // than capturing the full `BufferTurn` — under slow filesystem\n // latency the chain can back up, and we must not retain the\n // (potentially large) `turn.content` string for every pending\n // append.\n this.queueSurpriseTelemetryWrite({\n bufferKey,\n turnRole: turn.role,\n sessionKey:\n typeof turn.sessionKey === \"string\" ? turn.sessionKey : null,\n surpriseScore: surprise,\n triggered,\n turnCountInWindow: mutation.turnCountInWindow,\n // Stamp at decision time so backpressure on the write chain\n // does not shift the event's apparent moment away from when\n // the turn was actually scored.\n timestamp: new Date().toISOString(),\n // Snapshot the threshold used to compute `triggered` so a\n // concurrent config mutation cannot retroactively change\n // what the ledger row claims the decision was against.\n threshold: this.config.bufferSurpriseThreshold,\n });\n }\n }\n\n log.debug(\n `buffer[${bufferKey}]: ${mutation.turnCountInWindow} turns, signal=${mutation.signalLevel}, decision=${decision}`,\n );\n return extractionTurns ? { decision, extractionTurns } : { decision };\n }\n\n private async recordTurnUnlocked(bufferKey: string, turn: BufferTurn): Promise<AddTurnMutationResult> {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n const priorTurns = entry.turns.slice();\n entry.turns.push(turn);\n const activeTurnsSnapshot = entry.turns.map(copyBufferTurn);\n const retainedTurnsSnapshot = (entry.retainedTurns ?? []).map(copyBufferTurn);\n if (bufferKey === \"default\") {\n this.state.turns = entry.turns;\n }\n\n const signal = scanSignals(turn.content, this.config.highSignalPatterns);\n const decision = this.evaluate(entry, signal.level);\n const turnCountInWindow = entry.turns.length;\n\n this.pruneEntries([bufferKey]);\n await this.saveUnlocked();\n return {\n decision,\n signalLevel: signal.level,\n priorTurns,\n activeTurnsSnapshot,\n retainedTurnsSnapshot,\n turnCountInWindow,\n };\n }\n\n private async getExtractionTurnsIfBufferSnapshotStillCurrent(\n bufferKey: string,\n activeTurnsSnapshot: readonly BufferTurn[],\n retainedTurnsSnapshot: readonly BufferTurn[],\n ): Promise<BufferTurn[] | null> {\n return this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.peekEntry(bufferKey);\n if (!entry) return null;\n const retained = entry.retainedTurns ?? [];\n const stillCurrent =\n bufferTurnArrayIsSuffixOfSnapshot(entry.turns, activeTurnsSnapshot) &&\n bufferTurnArraysEqual(retained, retainedTurnsSnapshot);\n if (!stillCurrent) return null;\n return [...retained, ...entry.turns];\n });\n }\n\n /**\n * Enqueue a telemetry append on the serialized write chain.\n *\n * The chain is a classic `writeChain = writeChain.then(fn).catch(...)`\n * — each link waits for the previous to settle before its append\n * starts, so out-of-order chronology cannot happen even under\n * variable filesystem latency. We always attach `.catch` so one\n * rejection does not poison the chain for the rest of the session\n * (CLAUDE.md rule #40). The error is logged through\n * `emitSurpriseEventSafe` itself, which swallows its own rejections.\n *\n * Public surface is deliberately narrow — only `addTurn` should call\n * this, so the surprise telemetry path stays centralized.\n */\n private queueSurpriseTelemetryWrite(params: SurpriseTelemetryQueueEntry): void {\n this.surpriseTelemetryWriteChain = this.surpriseTelemetryWriteChain\n .then(() => this.emitSurpriseEventSafe(params))\n .catch(() => {\n // `emitSurpriseEventSafe` already handles the logging. We\n // swallow here only so one failure does not break the chain\n // for future writes.\n });\n }\n\n /**\n * Append a single `BUFFER_SURPRISE` telemetry row (issue #563 PR 3).\n *\n * Deliberately swallows write errors: the buffer must never fail to\n * record a turn because the observation ledger is read-only, out of\n * disk, or otherwise unhappy. The log line at debug lets operators\n * confirm the path fired without polluting the error channel.\n */\n private async emitSurpriseEventSafe(\n params: SurpriseTelemetryQueueEntry,\n ): Promise<void> {\n const storage = this.storage as StorageManager & {\n appendBufferSurpriseEvents?: (\n events: BufferSurpriseEvent[],\n ) => Promise<number>;\n };\n if (typeof storage.appendBufferSurpriseEvents !== \"function\") {\n // Older StorageManager / test double without the telemetry sink.\n // Silently skip — core path is still covered by the log line above.\n return;\n }\n const event: BufferSurpriseEvent = {\n event: \"BUFFER_SURPRISE\",\n // Use the decision-time stamp captured when the event was\n // queued, NOT `Date.now()` here — backpressure on the write\n // chain could otherwise shift timestamps into the future relative\n // to when the turn was scored.\n timestamp: params.timestamp,\n bufferKey: params.bufferKey,\n sessionKey: params.sessionKey,\n turnRole: params.turnRole,\n surpriseScore: params.surpriseScore,\n // Use the snapshotted threshold from the queue entry, not the\n // live config — see `SurpriseTelemetryQueueEntry.threshold`\n // doc for the rationale.\n threshold: params.threshold,\n triggeredFlush: params.triggered,\n turnCountInWindow: params.turnCountInWindow,\n };\n try {\n await storage.appendBufferSurpriseEvents([event]);\n } catch (err) {\n // Same guard as `computeSurpriseSafe`: non-Error rejections must\n // not crash the telemetry helper, which would defeat the whole\n // point of isolating the ledger write from the hot path.\n log.debug(\n `buffer[${params.bufferKey}]: surprise telemetry write failed, continuing: ${describeError(err)}`,\n );\n }\n }\n\n /**\n * Invoke the injected surprise probe defensively. Any error (probe throws,\n * embedder unavailable, timeout) is swallowed and logged at debug: the\n * surprise path must never crash the happy-path trigger evaluation. A\n * `null` return indicates \"no score available, fall through to existing\n * triggers\".\n */\n private async computeSurpriseSafe(\n bufferKey: string,\n turn: BufferTurn,\n priorTurns: readonly BufferTurn[],\n ): Promise<number | null> {\n if (!this.surpriseProbe) return null;\n try {\n // Hard timeout around the probe so a hung embedder cannot stall\n // `addTurn()` before `save()`. A slow probe would otherwise\n // prevent the just-appended turn from ever being persisted. The\n // timeout is a soft bound — we race it against the probe, take\n // whichever settles first, and treat the timeout as\n // \"probe unavailable, fall through\" rather than an error that\n // surfaces to the caller.\n const score = await probeWithTimeout(\n this.surpriseProbe.scoreTurn(bufferKey, turn, priorTurns),\n this.config.bufferSurpriseProbeTimeoutMs,\n );\n if (score === null) return null;\n if (typeof score !== \"number\" || !Number.isFinite(score)) {\n log.debug(\n `buffer[${bufferKey}]: surprise probe returned non-finite score (${String(score)}), ignoring`,\n );\n return null;\n }\n // Defensive clamp: formula lives in buffer-surprise.ts, but we never\n // want a misbehaving probe to inject an out-of-range value into the\n // threshold comparison.\n if (score < 0) return 0;\n if (score > 1) return 1;\n return score;\n } catch (err) {\n // `err` may be any thrown value — `throw null` and\n // `Promise.reject(\"x\")` are both legal. Accessing `.message` on a\n // non-Error would itself throw and defeat the failure-isolation\n // contract, so describe the value safely.\n log.debug(\n `buffer[${bufferKey}]: surprise probe failed, falling back to existing triggers: ${describeError(err)}`,\n );\n return null;\n }\n }\n\n private evaluate(entry: BufferEntryState, signalLevel: SignalLevel): TriggerDecision {\n if (this.config.triggerMode === \"smart\") {\n if (signalLevel === \"high\") return \"extract_now\";\n\n if (entry.turns.length >= this.config.bufferMaxTurns) {\n return \"extract_batch\";\n }\n\n if (entry.lastExtractionAt) {\n const elapsed =\n Date.now() - new Date(entry.lastExtractionAt).getTime();\n if (elapsed >= this.config.bufferMaxMinutes * 60_000) {\n return \"extract_batch\";\n }\n }\n\n return \"keep_buffering\";\n }\n\n if (this.config.triggerMode === \"every_n\") {\n return entry.turns.length >= this.config.bufferMaxTurns\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n\n if (this.config.triggerMode === \"time_based\") {\n if (!entry.lastExtractionAt) {\n return entry.turns.length >= this.config.bufferMaxTurns\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n const elapsed =\n Date.now() - new Date(entry.lastExtractionAt).getTime();\n return elapsed >= this.config.bufferMaxMinutes * 60_000\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n\n return \"keep_buffering\";\n }\n\n getTurns(bufferKey = \"default\"): BufferTurn[] {\n const entry = this.peekEntry(bufferKey);\n if (!entry) return [];\n const retained = entry.retainedTurns ?? [];\n // Retained turns (from a previous defer verdict, issue #562 PR 2) are\n // prepended so the chronological order — oldest context first — is\n // preserved for the next extraction pass.\n return [...retained, ...entry.turns];\n }\n\n /**\n * Retain a subset of the current turns across `clearAfterExtraction` so a\n * future extraction pass sees the context behind a deferred candidate\n * (issue #562, PR 2). Callers pass the turns that were seen during the\n * current extraction; the buffer keeps the tail (latest `max` turns) as\n * the retention window. Passing an empty array or `max <= 0` clears the\n * retention slot instead.\n */\n async retainDeferredTurns(\n bufferKey: string,\n turns: BufferTurn[],\n max = 10,\n ): Promise<void> {\n await this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n if (!Array.isArray(turns) || turns.length === 0 || max <= 0) {\n delete entry.retainedTurns;\n } else {\n // Guard `slice(-max)` against `max === 0` (CLAUDE.md gotcha 27):\n // `slice(-0)` equals `slice(0)` and would return ALL entries. We\n // already early-return above when max <= 0.\n const tail = turns.slice(-max);\n // Copy explicit fields only — never spread an external object into a\n // plain object because spread preserves any own `__proto__` /\n // `constructor` keys that may have arrived via JSON deserialization\n // of untrusted input (CodeQL js/prototype-polluting-assignment).\n entry.retainedTurns = tail.map<BufferTurn>((t) => {\n const copy: BufferTurn = {\n role: t.role,\n content: typeof t.content === \"string\" ? t.content : \"\",\n timestamp:\n typeof t.timestamp === \"string\"\n ? t.timestamp\n : new Date().toISOString(),\n };\n if (typeof t.sessionKey === \"string\") copy.sessionKey = t.sessionKey;\n if (typeof t.logicalSessionKey === \"string\") {\n copy.logicalSessionKey = t.logicalSessionKey;\n }\n if (\n t.providerThreadId === null ||\n typeof t.providerThreadId === \"string\"\n ) {\n copy.providerThreadId = t.providerThreadId;\n }\n if (typeof t.turnFingerprint === \"string\") {\n copy.turnFingerprint = t.turnFingerprint;\n }\n if (typeof t.persistProcessedFingerprint === \"boolean\") {\n copy.persistProcessedFingerprint = t.persistProcessedFingerprint;\n }\n return copy;\n });\n }\n await this.saveUnlocked();\n });\n }\n\n /**\n * Return the current retention window (issue #562, PR 2). Primarily for\n * tests and diagnostics.\n */\n getRetainedDeferredTurns(bufferKey = \"default\"): BufferTurn[] {\n const entry = this.peekEntry(bufferKey);\n return entry?.retainedTurns ? [...entry.retainedTurns] : [];\n }\n\n async findBufferKeyForSession(sessionKey: string): Promise<string | null> {\n const bufferKeys = await this.findBufferKeysForSession(sessionKey);\n return bufferKeys[0] ?? null;\n }\n\n async findBufferKeysForSession(sessionKey: string): Promise<string[]> {\n if (typeof sessionKey !== \"string\" || sessionKey.length === 0) return [];\n await this.mutationChain.catch(() => {});\n await this.load();\n\n const matches: string[] = [];\n const directEntry = this.peekEntry(sessionKey);\n if ((directEntry?.turns.length ?? 0) > 0) {\n matches.push(sessionKey);\n }\n\n const entries = this.state.entries ?? {};\n for (const [bufferKey, entry] of Object.entries(entries)) {\n if (\n !matches.includes(bufferKey) &&\n entry.turns.some(\n (turn) =>\n typeof turn.sessionKey === \"string\" && turn.sessionKey === sessionKey,\n )\n ) {\n matches.push(bufferKey);\n }\n }\n\n return matches;\n }\n\n async clearAfterExtraction(\n bufferKey = \"default\",\n extractedTurns?: readonly BufferTurn[],\n ): Promise<void> {\n await this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n if (Array.isArray(extractedTurns)) {\n const liveExtractedTurns = liveTurnsFromExtractionSnapshot(\n entry,\n extractedTurns,\n );\n let clearedLiveTurns = false;\n if (liveExtractedTurns.length > 0) {\n const matchedCount = matchingQueuedExtractionPrefixLength(\n entry.turns,\n liveExtractedTurns,\n );\n if (matchedCount > 0) {\n entry.turns = entry.turns.slice(matchedCount);\n clearedLiveTurns = true;\n } else {\n log.debug(\n `buffer[${bufferKey}]: extraction clear skipped because live turns changed before clear`,\n );\n }\n }\n if (!clearedLiveTurns) {\n await this.saveUnlocked();\n return;\n }\n } else {\n entry.turns = [];\n }\n entry.lastExtractionAt = new Date().toISOString();\n entry.extractionCount += 1;\n if (bufferKey === \"default\") {\n this.state.turns = entry.turns;\n this.state.lastExtractionAt = entry.lastExtractionAt;\n this.state.extractionCount = entry.extractionCount;\n }\n this.pruneEntries([bufferKey]);\n await this.saveUnlocked();\n });\n }\n\n getExtractionCount(bufferKey = \"default\"): number {\n return this.peekEntry(bufferKey)?.extractionCount ?? 0;\n }\n\n /**\n * Await any pending `BUFFER_SURPRISE` telemetry writes.\n *\n * The telemetry path is fire-and-forget from the hot path's point of\n * view, but tests and before-exit hooks sometimes need to make sure\n * the ledger has been flushed before they assert on its contents or\n * close the process. This method resolves once the current chain\n * head has settled; new writes scheduled after this call return a\n * separate, later settlement.\n *\n * Never throws — the chain already catches its own rejections.\n */\n async flushSurpriseTelemetry(): Promise<void> {\n await this.surpriseTelemetryWriteChain;\n }\n}\n\n/**\n * Render an arbitrary thrown value as a short string for debug logging.\n *\n * JavaScript permits throwing *any* value (`throw null`,\n * `Promise.reject(\"x\")`, `throw { reason: \"timeout\" }`) — not just\n * `Error` instances. The defensive catch blocks in `SmartBuffer` must\n * never themselves throw while trying to log the failure, or they\n * would defeat the whole point of isolating the surprise path from the\n * core extraction decision.\n */\nfunction describeError(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (err === null) return \"null\";\n if (err === undefined) return \"undefined\";\n if (typeof err === \"string\") return err;\n try {\n return JSON.stringify(err);\n } catch {\n return String(err);\n }\n}\n\nfunction copyBufferTurn(turn: BufferTurn): BufferTurn {\n const copy: BufferTurn = {\n role: turn.role,\n content: turn.content,\n timestamp: turn.timestamp,\n };\n if (typeof turn.sessionKey === \"string\") copy.sessionKey = turn.sessionKey;\n if (typeof turn.logicalSessionKey === \"string\") {\n copy.logicalSessionKey = turn.logicalSessionKey;\n }\n if (\n turn.providerThreadId === null ||\n typeof turn.providerThreadId === \"string\"\n ) {\n copy.providerThreadId = turn.providerThreadId;\n }\n if (typeof turn.turnFingerprint === \"string\") {\n copy.turnFingerprint = turn.turnFingerprint;\n }\n if (typeof turn.persistProcessedFingerprint === \"boolean\") {\n copy.persistProcessedFingerprint = turn.persistProcessedFingerprint;\n }\n return copy;\n}\n\nfunction bufferTurnsEqual(left: BufferTurn | undefined, right: BufferTurn): boolean {\n if (!left) return false;\n return (\n left.role === right.role &&\n left.content === right.content &&\n left.timestamp === right.timestamp &&\n left.sessionKey === right.sessionKey &&\n left.logicalSessionKey === right.logicalSessionKey &&\n left.providerThreadId === right.providerThreadId &&\n left.turnFingerprint === right.turnFingerprint &&\n left.persistProcessedFingerprint === right.persistProcessedFingerprint\n );\n}\n\nfunction bufferTurnArraysEqual(\n left: readonly BufferTurn[],\n right: readonly BufferTurn[],\n): boolean {\n return (\n left.length === right.length &&\n left.every((turn, index) => bufferTurnsEqual(turn, right[index]))\n );\n}\n\nfunction bufferTurnArrayIsSuffixOfSnapshot(\n liveTurns: readonly BufferTurn[],\n snapshot: readonly BufferTurn[],\n): boolean {\n if (liveTurns.length === 0 || liveTurns.length > snapshot.length) {\n return false;\n }\n const offset = snapshot.length - liveTurns.length;\n return liveTurns.every((turn, index) =>\n bufferTurnsEqual(turn, snapshot[offset + index]),\n );\n}\n\nfunction liveTurnsFromExtractionSnapshot(\n entry: BufferEntryState,\n extractedTurns: readonly BufferTurn[],\n): readonly BufferTurn[] {\n const retainedTurns = entry.retainedTurns ?? [];\n if (\n retainedTurns.length > 0 &&\n extractedTurns.length >= retainedTurns.length &&\n retainedTurns.every((turn, index) =>\n bufferTurnsEqual(extractedTurns[index], turn),\n )\n ) {\n const withoutRetainedPrefix = extractedTurns.slice(retainedTurns.length);\n if (\n withoutRetainedPrefix.length > 0 &&\n matchingPrefixLength(entry.turns, withoutRetainedPrefix) > 0\n ) {\n return withoutRetainedPrefix;\n }\n }\n return extractedTurns;\n}\n\nfunction matchingPrefixLength(\n liveTurns: readonly BufferTurn[],\n extractedTurns: readonly BufferTurn[],\n): number {\n let index = 0;\n while (\n index < liveTurns.length &&\n index < extractedTurns.length &&\n bufferTurnsEqual(liveTurns[index], extractedTurns[index])\n ) {\n index += 1;\n }\n return index;\n}\n\nfunction matchingQueuedExtractionPrefixLength(\n liveTurns: readonly BufferTurn[],\n extractedTurns: readonly BufferTurn[],\n): number {\n let bestMatchedCount = 0;\n for (let start = 0; start < extractedTurns.length; start += 1) {\n const matchedCount = matchingPrefixLength(\n liveTurns,\n extractedTurns.slice(start),\n );\n if (matchedCount > bestMatchedCount) {\n bestMatchedCount = matchedCount;\n if (bestMatchedCount === liveTurns.length) break;\n }\n }\n return bestMatchedCount;\n}\n\n/**\n * Sentinel error class for the probe timeout path. Catching it via\n * `instanceof` lets the buffer's surprise helper distinguish a timeout\n * from a probe rejection (which could carry operational context the\n * operator wants to see).\n */\nclass ProbeTimeoutError extends Error {\n constructor(timeoutMs: number) {\n super(`probe exceeded ${timeoutMs}ms`);\n this.name = \"ProbeTimeoutError\";\n }\n}\n\n/**\n * Race `inflight` against a timeout clock. Resolves with `inflight`'s\n * value if it settles first, otherwise rejects with `ProbeTimeoutError`.\n * The timer is cleared in both branches so a fast-resolving probe does\n * not leak a handle that would keep the Node event loop alive.\n */\nfunction probeWithTimeout<T>(\n inflight: Promise<T>,\n timeoutMs: number,\n): Promise<T> {\n let timer: NodeJS.Timeout | null = null;\n const timeout = new Promise<T>((_, reject) => {\n timer = setTimeout(() => reject(new ProbeTimeoutError(timeoutMs)), timeoutMs);\n // `.unref()` so the timer does not hold the event loop open if the\n // caller decides the probe result is no longer interesting.\n if (typeof (timer as NodeJS.Timeout).unref === \"function\") {\n (timer as NodeJS.Timeout).unref();\n }\n });\n return Promise.race([inflight, timeout]).finally(() => {\n if (timer) clearTimeout(timer);\n });\n}\n"],"mappings":";;;;;;;;AA8CA,IAAM,yBAAyB;AA8CxB,IAAM,cAAN,MAAkB;AAAA,EAsBvB,YACmB,QACA,SACjB,gBAA4C,MAC5C;AAHiB;AACA;AAGjB,SAAK,QAAQ,EAAE,OAAO,CAAC,GAAG,kBAAkB,MAAM,iBAAiB,EAAE;AACrE,SAAK,gBAAgB;AAAA,EACvB;AAAA,EANmB;AAAA,EACA;AAAA,EAvBX;AAAA,EACA,SAAS;AAAA,EACT,cAAoC;AAAA,EAC3B;AAAA,EACT,gBAAkC,QAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAelD,8BAAgD,QAAQ,QAAQ;AAAA,EAWhE,gBAAmB,IAAkC;AAC3D,UAAM,MAAM,KAAK,cAAc,MAAM,MAAM;AAAA,IAAC,CAAC,EAAE,KAAK,EAAE;AACtD,SAAK,gBAAgB,IAAI,MAAM,MAAM;AAAA,IAAC,CAAC;AACvC,WAAO;AAAA,EACT;AAAA,EAEQ,SAAS,KAA+B;AAC9C,QAAI,CAAC,KAAK,MAAM,SAAS;AACvB,WAAK,MAAM,UAAU,uBAAO,OAAO,IAAI;AAAA,IACzC;AACA,UAAM,UAAU,KAAK,MAAM;AAC3B,QAAI,OAAO,OAAO,SAAS,GAAG,GAAG;AAC/B,YAAM,SAAS,QAAQ,GAAG;AAG1B,UAAI,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,KAAK,GAAG;AACvE,eAAO;AAAA,MACT;AAAA,IAEF;AACA,UAAM,UAA4B;AAAA,MAChC,OAAO,CAAC;AAAA,MACR,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,IACnB;AACA,YAAQ,GAAG,IAAI;AACf,WAAO;AAAA,EACT;AAAA,EAEQ,UAAU,KAAsC;AACtD,UAAM,WAAW,KAAK,MAAM,UAAU,GAAG;AACzC,QAAI,SAAU,QAAO;AACrB,QAAI,QAAQ,UAAW,QAAO;AAC9B,WAAO;AAAA,MACL,OAAO,MAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,QAAQ,CAAC;AAAA,MAC7D,kBAAkB,KAAK,MAAM,oBAAoB;AAAA,MACjD,iBACE,OAAO,KAAK,MAAM,oBAAoB,WAAW,KAAK,MAAM,kBAAkB;AAAA,IAClF;AAAA,EACF;AAAA,EAEQ,eAAe,OAAiC;AACtD,UAAM,UAAU,OAAO;AAAA,MACrB,uBAAO,OAAO,IAAI;AAAA,MAClB,MAAM,WAAW,CAAC;AAAA,IACpB;AACA,QAAI,CAAC,QAAQ,SAAS;AACpB,cAAQ,UAAU;AAAA,QAChB,OAAO,MAAM,QAAQ,MAAM,KAAK,IAAI,CAAC,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA,QACxD,kBAAkB,MAAM,oBAAoB;AAAA,QAC5C,iBACE,OAAO,MAAM,oBAAoB,WAAW,MAAM,kBAAkB;AAAA,MACxE;AAAA,IACF;AACA,WAAO;AAAA,MACL,OAAO,QAAQ,QAAQ;AAAA,MACvB,kBAAkB,QAAQ,QAAQ;AAAA,MAClC,iBAAiB,QAAQ,QAAQ;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBAAgB,OAAiC;AACvD,UAAM,aAAa,MAAM,MAAM,OAAO,CAAC,QAAQ,SAAS;AACtD,YAAM,SAAS,KAAK,MAAM,KAAK,SAAS;AACxC,aAAO,OAAO,SAAS,MAAM,IAAI,KAAK,IAAI,QAAQ,MAAM,IAAI;AAAA,IAC9D,GAAG,EAAE;AACL,UAAM,mBACJ,OAAO,MAAM,qBAAqB,WAC9B,KAAK,MAAM,MAAM,gBAAgB,IACjC,OAAO;AACb,WAAO,KAAK;AAAA,MACV;AAAA,MACA,OAAO,SAAS,gBAAgB,IAAI,mBAAmB;AAAA,IACzD;AAAA,EACF;AAAA,EAEQ,aAAa,YAA4B;AAC/C,UAAM,UAAU,KAAK,MAAM;AAC3B,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,OAAO,KAAK,OAAO;AAChC,QAAI,KAAK,UAAU,uBAAwB;AAE3C,UAAM,iBAAiB,IAAI,IAAI,KAAK,IAAI,CAAC,KAAK,UAAU,CAAC,KAAK,KAAK,CAAC,CAAC;AACrE,UAAM,YAAY,KACf,OAAO,CAAC,QAAQ,QAAQ,aAAa,CAAC,WAAW,SAAS,GAAG,CAAC,EAC9D,OAAO,CAAC,SAAS,QAAQ,GAAG,GAAG,MAAM,UAAU,OAAO,CAAC,EACvD,KAAK,CAAC,MAAM,UAAU;AACrB,YAAM,SAAS,KAAK,gBAAgB,QAAQ,IAAI,KAAK;AAAA,QACnD,OAAO,CAAC;AAAA,QACR,kBAAkB;AAAA,QAClB,iBAAiB;AAAA,MACnB,CAAC;AACD,YAAM,UAAU,KAAK,gBAAgB,QAAQ,KAAK,KAAK;AAAA,QACrD,OAAO,CAAC;AAAA,QACR,kBAAkB;AAAA,QAClB,iBAAiB;AAAA,MACnB,CAAC;AACD,UAAI,WAAW,QAAS,QAAO,SAAS;AACxC,cAAQ,eAAe,IAAI,IAAI,KAAK,MAAM,eAAe,IAAI,KAAK,KAAK;AAAA,IACzE,CAAC;AAEH,UAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,SAAS,sBAAsB;AACvE,eAAW,OAAO,UAAU,MAAM,GAAG,cAAc,GAAG;AACpD,aAAO,QAAQ,GAAG;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,KAAK,OAAQ;AACjB,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,cAAc,KAAK,QAAQ,WAAW,EACxC,KAAK,CAAC,UAAU;AACf,aAAK,QAAQ,KAAK,eAAe,KAAK;AACtC,aAAK,SAAS;AAAA,MAChB,CAAC,EACA,QAAQ,MAAM;AACb,aAAK,cAAc;AAAA,MACrB,CAAC;AAAA,IACL;AACA,UAAM,KAAK;AAAA,EACb;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,KAAK,gBAAgB,YAAY,KAAK,aAAa,CAAC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAqB;AACnB,SAAK,QAAQ,EAAE,OAAO,CAAC,GAAG,kBAAkB,MAAM,iBAAiB,EAAE;AACrE,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAc,eAA8B;AAC1C,UAAM,KAAK,QAAQ,WAAW,KAAK,KAAK;AAAA,EAC1C;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,KAAK,gBAAgB,YAAY,KAAK,aAAa,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,QAAQ,WAAmB,MAA4C;AAC3E,YAAQ,MAAM,KAAK,mBAAmB,WAAW,IAAI,GAAG;AAAA,EAC1D;AAAA,EAEA,MAAM,mBACJ,WACA,MACyB;AACzB,UAAM,WAAW,MAAM,KAAK,gBAAgB,MAAM,KAAK,mBAAmB,WAAW,IAAI,CAAC;AAC1F,QAAI,WAAW,SAAS;AACxB,QAAI;AAQJ,QACE,aAAa,oBACb,KAAK,OAAO,gCACZ,KAAK,kBAAkB;AAAA;AAAA;AAAA,IAIvB,KAAK,OAAO,gBAAgB,WAC5B,SAAS,gBAAgB,QACzB;AACA,YAAM,WAAW,MAAM,KAAK,oBAAoB,WAAW,MAAM,SAAS,UAAU;AACpF,UAAI,aAAa,MAAM;AACrB,cAAM,gBAAgB,WAAW,KAAK,OAAO;AAC7C,YAAI,YAAY;AAChB,YAAI,eAAe;AACjB,gBAAM,eAAe,MAAM,KAAK;AAAA,YAC9B;AAAA,YACA,SAAS;AAAA,YACT,SAAS;AAAA,UACX;AACA,cAAI,cAAc;AAChB,gBAAI;AAAA,cACF,UAAU,SAAS,eAAe,SAAS,QAAQ,CAAC,CAAC,gBAAgB,KAAK,OAAO,uBAAuB;AAAA,YAC1G;AACA,uBAAW;AACX,wBAAY;AACZ,8BAAkB;AAAA,UACpB,OAAO;AACL,gBAAI;AAAA,cACF,UAAU,SAAS,eAAe,SAAS,QAAQ,CAAC,CAAC;AAAA,YACvD;AAAA,UACF;AAAA,QACF;AAgBA,aAAK,4BAA4B;AAAA,UAC/B;AAAA,UACA,UAAU,KAAK;AAAA,UACf,YACE,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa;AAAA,UAC1D,eAAe;AAAA,UACf;AAAA,UACA,mBAAmB,SAAS;AAAA;AAAA;AAAA;AAAA,UAI5B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA;AAAA;AAAA;AAAA,UAIlC,WAAW,KAAK,OAAO;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI;AAAA,MACF,UAAU,SAAS,MAAM,SAAS,iBAAiB,kBAAkB,SAAS,WAAW,cAAc,QAAQ;AAAA,IACjH;AACA,WAAO,kBAAkB,EAAE,UAAU,gBAAgB,IAAI,EAAE,SAAS;AAAA,EACtE;AAAA,EAEA,MAAc,mBAAmB,WAAmB,MAAkD;AACpG,UAAM,KAAK,aAAa;AACxB,UAAM,QAAQ,KAAK,SAAS,SAAS;AACrC,UAAM,aAAa,MAAM,MAAM,MAAM;AACrC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,sBAAsB,MAAM,MAAM,IAAI,cAAc;AAC1D,UAAM,yBAAyB,MAAM,iBAAiB,CAAC,GAAG,IAAI,cAAc;AAC5E,QAAI,cAAc,WAAW;AAC3B,WAAK,MAAM,QAAQ,MAAM;AAAA,IAC3B;AAEA,UAAM,SAAS,YAAY,KAAK,SAAS,KAAK,OAAO,kBAAkB;AACvE,UAAM,WAAW,KAAK,SAAS,OAAO,OAAO,KAAK;AAClD,UAAM,oBAAoB,MAAM,MAAM;AAEtC,SAAK,aAAa,CAAC,SAAS,CAAC;AAC7B,UAAM,KAAK,aAAa;AACxB,WAAO;AAAA,MACL;AAAA,MACA,aAAa,OAAO;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,+CACZ,WACA,qBACA,uBAC8B;AAC9B,WAAO,KAAK,gBAAgB,YAAY;AACtC,YAAM,KAAK,aAAa;AACxB,YAAM,QAAQ,KAAK,UAAU,SAAS;AACtC,UAAI,CAAC,MAAO,QAAO;AACnB,YAAM,WAAW,MAAM,iBAAiB,CAAC;AACzC,YAAM,eACJ,kCAAkC,MAAM,OAAO,mBAAmB,KAClE,sBAAsB,UAAU,qBAAqB;AACvD,UAAI,CAAC,aAAc,QAAO;AAC1B,aAAO,CAAC,GAAG,UAAU,GAAG,MAAM,KAAK;AAAA,IACrC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBQ,4BAA4B,QAA2C;AAC7E,SAAK,8BAA8B,KAAK,4BACrC,KAAK,MAAM,KAAK,sBAAsB,MAAM,CAAC,EAC7C,MAAM,MAAM;AAAA,IAIb,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,sBACZ,QACe;AACf,UAAM,UAAU,KAAK;AAKrB,QAAI,OAAO,QAAQ,+BAA+B,YAAY;AAG5D;AAAA,IACF;AACA,UAAM,QAA6B;AAAA,MACjC,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,MAKP,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,MAClB,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,eAAe,OAAO;AAAA;AAAA;AAAA;AAAA,MAItB,WAAW,OAAO;AAAA,MAClB,gBAAgB,OAAO;AAAA,MACvB,mBAAmB,OAAO;AAAA,IAC5B;AACA,QAAI;AACF,YAAM,QAAQ,2BAA2B,CAAC,KAAK,CAAC;AAAA,IAClD,SAAS,KAAK;AAIZ,UAAI;AAAA,QACF,UAAU,OAAO,SAAS,mDAAmD,cAAc,GAAG,CAAC;AAAA,MACjG;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,oBACZ,WACA,MACA,YACwB;AACxB,QAAI,CAAC,KAAK,cAAe,QAAO;AAChC,QAAI;AAQF,YAAM,QAAQ,MAAM;AAAA,QAClB,KAAK,cAAc,UAAU,WAAW,MAAM,UAAU;AAAA,QACxD,KAAK,OAAO;AAAA,MACd;AACA,UAAI,UAAU,KAAM,QAAO;AAC3B,UAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG;AACxD,YAAI;AAAA,UACF,UAAU,SAAS,gDAAgD,OAAO,KAAK,CAAC;AAAA,QAClF;AACA,eAAO;AAAA,MACT;AAIA,UAAI,QAAQ,EAAG,QAAO;AACtB,UAAI,QAAQ,EAAG,QAAO;AACtB,aAAO;AAAA,IACT,SAAS,KAAK;AAKZ,UAAI;AAAA,QACF,UAAU,SAAS,gEAAgE,cAAc,GAAG,CAAC;AAAA,MACvG;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,SAAS,OAAyB,aAA2C;AACnF,QAAI,KAAK,OAAO,gBAAgB,SAAS;AACvC,UAAI,gBAAgB,OAAQ,QAAO;AAEnC,UAAI,MAAM,MAAM,UAAU,KAAK,OAAO,gBAAgB;AACpD,eAAO;AAAA,MACT;AAEA,UAAI,MAAM,kBAAkB;AAC1B,cAAM,UACJ,KAAK,IAAI,IAAI,IAAI,KAAK,MAAM,gBAAgB,EAAE,QAAQ;AACxD,YAAI,WAAW,KAAK,OAAO,mBAAmB,KAAQ;AACpD,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,OAAO,gBAAgB,WAAW;AACzC,aAAO,MAAM,MAAM,UAAU,KAAK,OAAO,iBACrC,kBACA;AAAA,IACN;AAEA,QAAI,KAAK,OAAO,gBAAgB,cAAc;AAC5C,UAAI,CAAC,MAAM,kBAAkB;AAC3B,eAAO,MAAM,MAAM,UAAU,KAAK,OAAO,iBACrC,kBACA;AAAA,MACN;AACA,YAAM,UACJ,KAAK,IAAI,IAAI,IAAI,KAAK,MAAM,gBAAgB,EAAE,QAAQ;AACxD,aAAO,WAAW,KAAK,OAAO,mBAAmB,MAC7C,kBACA;AAAA,IACN;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,YAAY,WAAyB;AAC5C,UAAM,QAAQ,KAAK,UAAU,SAAS;AACtC,QAAI,CAAC,MAAO,QAAO,CAAC;AACpB,UAAM,WAAW,MAAM,iBAAiB,CAAC;AAIzC,WAAO,CAAC,GAAG,UAAU,GAAG,MAAM,KAAK;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,oBACJ,WACA,OACA,MAAM,IACS;AACf,UAAM,KAAK,gBAAgB,YAAY;AACrC,YAAM,KAAK,aAAa;AACxB,YAAM,QAAQ,KAAK,SAAS,SAAS;AACrC,UAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,KAAK,OAAO,GAAG;AAC3D,eAAO,MAAM;AAAA,MACf,OAAO;AAIL,cAAM,OAAO,MAAM,MAAM,CAAC,GAAG;AAK7B,cAAM,gBAAgB,KAAK,IAAgB,CAAC,MAAM;AAChD,gBAAM,OAAmB;AAAA,YACvB,MAAM,EAAE;AAAA,YACR,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU;AAAA,YACrD,WACE,OAAO,EAAE,cAAc,WACnB,EAAE,aACF,oBAAI,KAAK,GAAE,YAAY;AAAA,UAC/B;AACA,cAAI,OAAO,EAAE,eAAe,SAAU,MAAK,aAAa,EAAE;AAC1D,cAAI,OAAO,EAAE,sBAAsB,UAAU;AAC3C,iBAAK,oBAAoB,EAAE;AAAA,UAC7B;AACA,cACE,EAAE,qBAAqB,QACvB,OAAO,EAAE,qBAAqB,UAC9B;AACA,iBAAK,mBAAmB,EAAE;AAAA,UAC5B;AACA,cAAI,OAAO,EAAE,oBAAoB,UAAU;AACzC,iBAAK,kBAAkB,EAAE;AAAA,UAC3B;AACA,cAAI,OAAO,EAAE,gCAAgC,WAAW;AACtD,iBAAK,8BAA8B,EAAE;AAAA,UACvC;AACA,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AACA,YAAM,KAAK,aAAa;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,yBAAyB,YAAY,WAAyB;AAC5D,UAAM,QAAQ,KAAK,UAAU,SAAS;AACtC,WAAO,OAAO,gBAAgB,CAAC,GAAG,MAAM,aAAa,IAAI,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,wBAAwB,YAA4C;AACxE,UAAM,aAAa,MAAM,KAAK,yBAAyB,UAAU;AACjE,WAAO,WAAW,CAAC,KAAK;AAAA,EAC1B;AAAA,EAEA,MAAM,yBAAyB,YAAuC;AACpE,QAAI,OAAO,eAAe,YAAY,WAAW,WAAW,EAAG,QAAO,CAAC;AACvE,UAAM,KAAK,cAAc,MAAM,MAAM;AAAA,IAAC,CAAC;AACvC,UAAM,KAAK,KAAK;AAEhB,UAAM,UAAoB,CAAC;AAC3B,UAAM,cAAc,KAAK,UAAU,UAAU;AAC7C,SAAK,aAAa,MAAM,UAAU,KAAK,GAAG;AACxC,cAAQ,KAAK,UAAU;AAAA,IACzB;AAEA,UAAM,UAAU,KAAK,MAAM,WAAW,CAAC;AACvC,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACxD,UACE,CAAC,QAAQ,SAAS,SAAS,KAC3B,MAAM,MAAM;AAAA,QACV,CAAC,SACC,OAAO,KAAK,eAAe,YAAY,KAAK,eAAe;AAAA,MAC/D,GACA;AACA,gBAAQ,KAAK,SAAS;AAAA,MACxB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,qBACJ,YAAY,WACZ,gBACe;AACf,UAAM,KAAK,gBAAgB,YAAY;AACrC,YAAM,KAAK,aAAa;AACxB,YAAM,QAAQ,KAAK,SAAS,SAAS;AACrC,UAAI,MAAM,QAAQ,cAAc,GAAG;AACjC,cAAM,qBAAqB;AAAA,UACzB;AAAA,UACA;AAAA,QACF;AACA,YAAI,mBAAmB;AACvB,YAAI,mBAAmB,SAAS,GAAG;AACjC,gBAAM,eAAe;AAAA,YACnB,MAAM;AAAA,YACN;AAAA,UACF;AACA,cAAI,eAAe,GAAG;AACpB,kBAAM,QAAQ,MAAM,MAAM,MAAM,YAAY;AAC5C,+BAAmB;AAAA,UACrB,OAAO;AACL,gBAAI;AAAA,cACF,UAAU,SAAS;AAAA,YACrB;AAAA,UACF;AAAA,QACF;AACA,YAAI,CAAC,kBAAkB;AACrB,gBAAM,KAAK,aAAa;AACxB;AAAA,QACF;AAAA,MACF,OAAO;AACL,cAAM,QAAQ,CAAC;AAAA,MACjB;AACA,YAAM,oBAAmB,oBAAI,KAAK,GAAE,YAAY;AAChD,YAAM,mBAAmB;AACzB,UAAI,cAAc,WAAW;AAC3B,aAAK,MAAM,QAAQ,MAAM;AACzB,aAAK,MAAM,mBAAmB,MAAM;AACpC,aAAK,MAAM,kBAAkB,MAAM;AAAA,MACrC;AACA,WAAK,aAAa,CAAC,SAAS,CAAC;AAC7B,YAAM,KAAK,aAAa;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA,EAEA,mBAAmB,YAAY,WAAmB;AAChD,WAAO,KAAK,UAAU,SAAS,GAAG,mBAAmB;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,yBAAwC;AAC5C,UAAM,KAAK;AAAA,EACb;AACF;AAYA,SAAS,cAAc,KAAsB;AAC3C,MAAI,eAAe,MAAO,QAAO,IAAI;AACrC,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI,QAAQ,OAAW,QAAO;AAC9B,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,GAAG;AAAA,EACnB;AACF;AAEA,SAAS,eAAe,MAA8B;AACpD,QAAM,OAAmB;AAAA,IACvB,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,WAAW,KAAK;AAAA,EAClB;AACA,MAAI,OAAO,KAAK,eAAe,SAAU,MAAK,aAAa,KAAK;AAChE,MAAI,OAAO,KAAK,sBAAsB,UAAU;AAC9C,SAAK,oBAAoB,KAAK;AAAA,EAChC;AACA,MACE,KAAK,qBAAqB,QAC1B,OAAO,KAAK,qBAAqB,UACjC;AACA,SAAK,mBAAmB,KAAK;AAAA,EAC/B;AACA,MAAI,OAAO,KAAK,oBAAoB,UAAU;AAC5C,SAAK,kBAAkB,KAAK;AAAA,EAC9B;AACA,MAAI,OAAO,KAAK,gCAAgC,WAAW;AACzD,SAAK,8BAA8B,KAAK;AAAA,EAC1C;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAA8B,OAA4B;AAClF,MAAI,CAAC,KAAM,QAAO;AAClB,SACE,KAAK,SAAS,MAAM,QACpB,KAAK,YAAY,MAAM,WACvB,KAAK,cAAc,MAAM,aACzB,KAAK,eAAe,MAAM,cAC1B,KAAK,sBAAsB,MAAM,qBACjC,KAAK,qBAAqB,MAAM,oBAChC,KAAK,oBAAoB,MAAM,mBAC/B,KAAK,gCAAgC,MAAM;AAE/C;AAEA,SAAS,sBACP,MACA,OACS;AACT,SACE,KAAK,WAAW,MAAM,UACtB,KAAK,MAAM,CAAC,MAAM,UAAU,iBAAiB,MAAM,MAAM,KAAK,CAAC,CAAC;AAEpE;AAEA,SAAS,kCACP,WACA,UACS;AACT,MAAI,UAAU,WAAW,KAAK,UAAU,SAAS,SAAS,QAAQ;AAChE,WAAO;AAAA,EACT;AACA,QAAM,SAAS,SAAS,SAAS,UAAU;AAC3C,SAAO,UAAU;AAAA,IAAM,CAAC,MAAM,UAC5B,iBAAiB,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,EACjD;AACF;AAEA,SAAS,gCACP,OACA,gBACuB;AACvB,QAAM,gBAAgB,MAAM,iBAAiB,CAAC;AAC9C,MACE,cAAc,SAAS,KACvB,eAAe,UAAU,cAAc,UACvC,cAAc;AAAA,IAAM,CAAC,MAAM,UACzB,iBAAiB,eAAe,KAAK,GAAG,IAAI;AAAA,EAC9C,GACA;AACA,UAAM,wBAAwB,eAAe,MAAM,cAAc,MAAM;AACvE,QACE,sBAAsB,SAAS,KAC/B,qBAAqB,MAAM,OAAO,qBAAqB,IAAI,GAC3D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,qBACP,WACA,gBACQ;AACR,MAAI,QAAQ;AACZ,SACE,QAAQ,UAAU,UAClB,QAAQ,eAAe,UACvB,iBAAiB,UAAU,KAAK,GAAG,eAAe,KAAK,CAAC,GACxD;AACA,aAAS;AAAA,EACX;AACA,SAAO;AACT;AAEA,SAAS,qCACP,WACA,gBACQ;AACR,MAAI,mBAAmB;AACvB,WAAS,QAAQ,GAAG,QAAQ,eAAe,QAAQ,SAAS,GAAG;AAC7D,UAAM,eAAe;AAAA,MACnB;AAAA,MACA,eAAe,MAAM,KAAK;AAAA,IAC5B;AACA,QAAI,eAAe,kBAAkB;AACnC,yBAAmB;AACnB,UAAI,qBAAqB,UAAU,OAAQ;AAAA,IAC7C;AAAA,EACF;AACA,SAAO;AACT;AAQA,IAAM,oBAAN,cAAgC,MAAM;AAAA,EACpC,YAAY,WAAmB;AAC7B,UAAM,kBAAkB,SAAS,IAAI;AACrC,SAAK,OAAO;AAAA,EACd;AACF;AAQA,SAAS,iBACP,UACA,WACY;AACZ,MAAI,QAA+B;AACnC,QAAM,UAAU,IAAI,QAAW,CAAC,GAAG,WAAW;AAC5C,YAAQ,WAAW,MAAM,OAAO,IAAI,kBAAkB,SAAS,CAAC,GAAG,SAAS;AAG5E,QAAI,OAAQ,MAAyB,UAAU,YAAY;AACzD,MAAC,MAAyB,MAAM;AAAA,IAClC;AAAA,EACF,CAAC;AACD,SAAO,QAAQ,KAAK,CAAC,UAAU,OAAO,CAAC,EAAE,QAAQ,MAAM;AACrD,QAAI,MAAO,cAAa,KAAK;AAAA,EAC/B,CAAC;AACH;","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  compareEntityTimestamps,
6
6
  normalizeEntityName
7
- } from "./chunk-YFS5OEKO.js";
7
+ } from "./chunk-7MLB4NCL.js";
8
8
  import {
9
9
  sanitizeMemoryContent
10
10
  } from "./chunk-FVQJYWH7.js";
@@ -677,4 +677,4 @@ export {
677
677
  entityIndexVersion,
678
678
  entityRecentTranscriptLookbackHours
679
679
  };
680
- //# sourceMappingURL=chunk-DOX2CG6Y.js.map
680
+ //# sourceMappingURL=chunk-IEUU7O4F.js.map
@@ -59,7 +59,7 @@ var RemoteSearchBackend = class {
59
59
  }
60
60
  async embedCollection(_collection) {
61
61
  }
62
- async ensureCollection(_memoryDir, _execution) {
62
+ async ensureCollection(_memoryDir, _collectionOrExecution, _execution) {
63
63
  return "skipped";
64
64
  }
65
65
  headers() {
@@ -95,4 +95,4 @@ var RemoteSearchBackend = class {
95
95
  export {
96
96
  RemoteSearchBackend
97
97
  };
98
- //# sourceMappingURL=chunk-JNANKJLN.js.map
98
+ //# sourceMappingURL=chunk-JOASJWQR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/search/remote-backend.ts"],"sourcesContent":["import { log } from \"../logger.js\";\nimport type { SearchBackend, SearchExecutionOptions, SearchQueryOptions, SearchResult } from \"./port.js\";\n\nexport interface RemoteSearchBackendOptions {\n baseUrl: string;\n apiKey?: string;\n timeoutMs?: number;\n}\n\n/**\n * HTTP REST search backend adapter.\n *\n * Delegates search to a remote service. Maintenance methods are no-ops\n * (remote backends manage their own indexing).\n */\nexport class RemoteSearchBackend implements SearchBackend {\n private readonly baseUrl: string;\n private readonly apiKey?: string;\n private readonly timeoutMs: number;\n private available = false;\n\n constructor(opts: RemoteSearchBackendOptions) {\n let url = opts.baseUrl;\n while (url.endsWith(\"/\")) url = url.slice(0, -1);\n this.baseUrl = url;\n this.apiKey = opts.apiKey;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n }\n\n async probe(): Promise<boolean> {\n try {\n const res = await fetch(`${this.baseUrl}/health`, {\n method: \"GET\",\n headers: this.headers(),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n this.available = res.ok;\n return this.available;\n } catch (err) {\n log.debug(`RemoteSearchBackend probe failed: ${err}`);\n this.available = false;\n return false;\n }\n }\n\n isAvailable(): boolean {\n return this.available;\n }\n\n debugStatus(): string {\n return `backend=remote available=${this.available} baseUrl=${this.baseUrl}`;\n }\n\n async search(\n query: string,\n collection?: string,\n maxResults?: number,\n _options?: SearchQueryOptions,\n execution?: SearchExecutionOptions,\n ): Promise<SearchResult[]> {\n return this.post(\"/search/deep\", { query, collection, maxResults }, execution);\n }\n\n async searchGlobal(query: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n return this.post(\"/search/deep\", { query, maxResults }, execution);\n }\n\n async bm25Search(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n return this.post(\"/search/bm25\", { query, collection, maxResults }, execution);\n }\n\n async vectorSearch(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n return this.post(\"/search/vector\", { query, collection, maxResults }, execution);\n }\n\n async hybridSearch(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n return this.post(\"/search/hybrid\", { query, collection, maxResults }, execution);\n }\n\n async update(_execution?: SearchExecutionOptions): Promise<void> {}\n async updateCollection(_collection: string, _execution?: SearchExecutionOptions): Promise<void> {}\n async embed(): Promise<void> {}\n async embedCollection(_collection: string): Promise<void> {}\n\n async ensureCollection(\n _memoryDir: string,\n _collectionOrExecution?: string | SearchExecutionOptions,\n _execution?: SearchExecutionOptions,\n ): Promise<\"skipped\"> {\n return \"skipped\";\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (this.apiKey) {\n h[\"Authorization\"] = `Bearer ${this.apiKey}`;\n }\n return h;\n }\n\n private async post(\n endpoint: string,\n body: Record<string, unknown>,\n execution?: SearchExecutionOptions,\n ): Promise<SearchResult[]> {\n if (!this.available) return [];\n try {\n const res = await fetch(`${this.baseUrl}${endpoint}`, {\n method: \"POST\",\n headers: this.headers(),\n body: JSON.stringify(body),\n signal: execution?.signal\n ? AbortSignal.any([execution.signal, AbortSignal.timeout(this.timeoutMs)])\n : AbortSignal.timeout(this.timeoutMs),\n });\n if (!res.ok) {\n log.debug(`RemoteSearchBackend ${endpoint} returned ${res.status}`);\n return [];\n }\n const data = await res.json();\n if (!Array.isArray(data)) return [];\n return data as SearchResult[];\n } catch (err) {\n log.debug(`RemoteSearchBackend ${endpoint} failed: ${err}`);\n return [];\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseMessageParts
3
- } from "./chunk-XKIQZXUB.js";
3
+ } from "./chunk-CI7RKSRE.js";
4
4
  import {
5
5
  recordObjectiveStateSnapshot
6
6
  } from "./chunk-UEY3VB6W.js";
@@ -644,4 +644,4 @@ export {
644
644
  recordObjectiveStateSnapshotsFromAgentMessages,
645
645
  recordObjectiveStateSnapshotsFromObservedMessages
646
646
  };
647
- //# sourceMappingURL=chunk-WSGF57U2.js.map
647
+ //# sourceMappingURL=chunk-JQDZQ4TB.js.map
@@ -94,9 +94,9 @@ var WebSearchProvider = class {
94
94
 
95
95
  // src/enrichment/pipeline.ts
96
96
  var rateBuckets = /* @__PURE__ */ new Map();
97
- function isRateLimited(provider, config) {
97
+ function reserveRateLimitSlot(provider, config) {
98
98
  const providerCfg = config.providers.find((p) => p.id === provider.id);
99
- if (!providerCfg?.rateLimit) return false;
99
+ if (!providerCfg?.rateLimit) return true;
100
100
  const now = Date.now();
101
101
  let bucket = rateBuckets.get(provider.id);
102
102
  if (!bucket) {
@@ -117,14 +117,12 @@ function isRateLimited(provider, config) {
117
117
  bucket.dayReset = now + 864e5;
118
118
  }
119
119
  const { maxPerMinute, maxPerDay } = providerCfg.rateLimit;
120
- return bucket.minuteCount >= maxPerMinute || bucket.dayCount >= maxPerDay;
121
- }
122
- function recordCall(providerId) {
123
- const bucket = rateBuckets.get(providerId);
124
- if (bucket) {
125
- bucket.minuteCount += 1;
126
- bucket.dayCount += 1;
120
+ if (bucket.minuteCount >= maxPerMinute || bucket.dayCount >= maxPerDay) {
121
+ return false;
127
122
  }
123
+ bucket.minuteCount += 1;
124
+ bucket.dayCount += 1;
125
+ return true;
128
126
  }
129
127
  async function runEnrichmentPipeline(entities, registry, config, log) {
130
128
  if (!config.enabled) return [];
@@ -161,7 +159,7 @@ async function runEnrichmentPipeline(entities, registry, config, log) {
161
159
  });
162
160
  continue;
163
161
  }
164
- if (isRateLimited(provider, config)) {
162
+ if (!reserveRateLimitSlot(provider, config)) {
165
163
  log.debug?.(
166
164
  `enrichment: skipping provider ${provider.id} for ${entity.name} \u2014 rate limited`
167
165
  );
@@ -180,7 +178,6 @@ async function runEnrichmentPipeline(entities, registry, config, log) {
180
178
  try {
181
179
  candidates = await provider.enrich(entity);
182
180
  } catch (err) {
183
- recordCall(provider.id);
184
181
  log.error?.(
185
182
  `enrichment: provider ${provider.id} failed for ${entity.name}: ${err instanceof Error ? err.message : String(err)}`
186
183
  );
@@ -195,7 +192,6 @@ async function runEnrichmentPipeline(entities, registry, config, log) {
195
192
  });
196
193
  continue;
197
194
  }
198
- recordCall(provider.id);
199
195
  for (const candidate of candidates) {
200
196
  candidate.source = provider.id;
201
197
  }
@@ -272,4 +268,4 @@ export {
272
268
  appendAuditEntry,
273
269
  readAuditLog
274
270
  };
275
- //# sourceMappingURL=chunk-HINSGUA7.js.map
271
+ //# sourceMappingURL=chunk-KBL3JJR6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/enrichment/types.ts","../src/enrichment/provider-registry.ts","../src/enrichment/web-search-provider.ts","../src/enrichment/pipeline.ts","../src/enrichment/audit.ts"],"sourcesContent":["/**\n * Enrichment pipeline types (issue #365).\n *\n * Defines the provider interface, candidate shape, pipeline config,\n * and result types for the external enrichment subsystem.\n */\n\nimport type { ImportanceLevel, MemoryCategory } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Provider config & interface\n// ---------------------------------------------------------------------------\n\nexport type EnrichmentCostTier = \"free\" | \"cheap\" | \"expensive\";\n\nexport interface EnrichmentProviderConfig {\n id: string;\n enabled: boolean;\n costTier: EnrichmentCostTier;\n rateLimit?: { maxPerMinute: number; maxPerDay: number };\n}\n\nexport interface EnrichmentCandidate {\n text: string;\n source: string;\n sourceUrl?: string;\n confidence: number;\n category: MemoryCategory;\n tags?: string[];\n}\n\nexport interface EnrichmentProvider {\n readonly id: string;\n readonly costTier: EnrichmentCostTier;\n enrich(entity: EntityEnrichmentInput): Promise<EnrichmentCandidate[]>;\n isAvailable(): Promise<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Entity enrichment input\n// ---------------------------------------------------------------------------\n\nexport interface EntityEnrichmentInput {\n name: string;\n type: string;\n knownFacts: string[];\n importanceLevel: ImportanceLevel;\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline result\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentResult {\n entityName: string;\n provider: string;\n candidatesFound: number;\n candidatesAccepted: number;\n candidatesRejected: number;\n acceptedCandidates: EnrichmentCandidate[];\n elapsed: number;\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline config\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentPipelineConfig {\n enabled: boolean;\n providers: EnrichmentProviderConfig[];\n importanceThresholds: {\n critical: string[];\n high: string[];\n normal: string[];\n low: string[];\n };\n maxCandidatesPerEntity: number;\n autoEnrichOnCreate: boolean;\n scheduleIntervalMs: number;\n}\n\n/**\n * Build a default (disabled) pipeline config. Every consumer that needs a\n * config object should call this rather than duplicating the defaults.\n */\nexport function defaultEnrichmentPipelineConfig(): EnrichmentPipelineConfig {\n return {\n enabled: false,\n providers: [],\n importanceThresholds: {\n critical: [],\n high: [],\n normal: [],\n low: [],\n },\n maxCandidatesPerEntity: 20,\n autoEnrichOnCreate: false,\n scheduleIntervalMs: 3_600_000,\n };\n}\n","/**\n * Enrichment provider registry (issue #365).\n *\n * Central registry for enrichment providers. Providers register themselves\n * at startup; the pipeline queries the registry to determine which providers\n * to run for a given importance tier.\n */\n\nimport type { ImportanceLevel } from \"../types.js\";\nimport type {\n EnrichmentPipelineConfig,\n EnrichmentProvider,\n} from \"./types.js\";\n\nexport class EnrichmentProviderRegistry {\n private readonly providers = new Map<string, EnrichmentProvider>();\n\n /** Register a provider. Overwrites any existing provider with the same id. */\n register(provider: EnrichmentProvider): void {\n this.providers.set(provider.id, provider);\n }\n\n /** Look up a single provider by id. */\n get(id: string): EnrichmentProvider | undefined {\n return this.providers.get(id);\n }\n\n /**\n * Return all registered providers whose id appears in the config's\n * `providers` list with `enabled: true`.\n */\n listEnabled(config: EnrichmentPipelineConfig): EnrichmentProvider[] {\n const enabledIds = new Set(\n config.providers\n .filter((p) => p.enabled)\n .map((p) => p.id),\n );\n const result: EnrichmentProvider[] = [];\n for (const [id, provider] of this.providers.entries()) {\n if (enabledIds.has(id)) {\n result.push(provider);\n }\n }\n return result;\n }\n\n /**\n * Return providers that should run for a given importance level.\n * Providers are resolved from `config.importanceThresholds[level]` and\n * filtered to only those that are both registered and enabled.\n */\n getForImportance(\n level: ImportanceLevel,\n config: EnrichmentPipelineConfig,\n ): EnrichmentProvider[] {\n // \"trivial\" entities never get enrichment providers\n if (level === \"trivial\") return [];\n\n const thresholds = config.importanceThresholds;\n const providerIds: string[] =\n level === \"critical\"\n ? thresholds.critical\n : level === \"high\"\n ? thresholds.high\n : level === \"normal\"\n ? thresholds.normal\n : thresholds.low;\n\n const enabledIds = new Set(\n config.providers\n .filter((p) => p.enabled)\n .map((p) => p.id),\n );\n\n const result: EnrichmentProvider[] = [];\n for (const id of providerIds) {\n if (!enabledIds.has(id)) continue;\n const provider = this.providers.get(id);\n if (provider) {\n result.push(provider);\n }\n }\n return result;\n }\n}\n","/**\n * Web search enrichment provider stub (issue #365).\n *\n * A basic provider backed by web search. Since this is opt-in and we do not\n * want to hard-code an API key, the provider accepts an optional `searchFn`\n * injection point. When no search function is configured it returns empty\n * results, making it safe to register unconditionally.\n */\n\nimport type {\n EnrichmentCandidate,\n EnrichmentCostTier,\n EnrichmentProvider,\n EntityEnrichmentInput,\n} from \"./types.js\";\n\nexport type WebSearchFn = (query: string) => Promise<string[]>;\n\nexport interface WebSearchProviderOptions {\n /**\n * Injected search function. Each returned string is treated as a raw\n * snippet. When `undefined` the provider returns empty results.\n */\n searchFn?: WebSearchFn;\n}\n\nexport class WebSearchProvider implements EnrichmentProvider {\n readonly id = \"web-search\";\n readonly costTier: EnrichmentCostTier = \"cheap\";\n\n private readonly searchFn: WebSearchFn | undefined;\n\n constructor(options: WebSearchProviderOptions = {}) {\n this.searchFn = options.searchFn;\n }\n\n async isAvailable(): Promise<boolean> {\n return this.searchFn !== undefined;\n }\n\n async enrich(entity: EntityEnrichmentInput): Promise<EnrichmentCandidate[]> {\n if (!this.searchFn) return [];\n\n const query = `${entity.name} ${entity.type}`;\n const snippets = await this.searchFn(query);\n\n return snippets\n .filter((s) => typeof s === \"string\" && s.trim().length > 0)\n .map((snippet) => ({\n text: snippet.trim(),\n source: this.id,\n sourceUrl: undefined,\n confidence: 0.5,\n category: \"fact\" as const,\n tags: [\"web-search\"],\n }));\n }\n}\n","/**\n * Enrichment pipeline orchestrator (issue #365).\n *\n * For each entity, determines the importance tier, resolves the providers\n * to run, executes them in sequence (respecting rate limits), tags\n * candidates, and caps at `maxCandidatesPerEntity`.\n *\n * Accepted candidates are returned in each `EnrichmentResult` via the\n * `acceptedCandidates` field so that callers can persist them.\n */\n\nimport type { LoggerBackend } from \"../logger.js\";\nimport type { EnrichmentProviderRegistry } from \"./provider-registry.js\";\nimport type {\n EnrichmentCandidate,\n EnrichmentPipelineConfig,\n EnrichmentProvider,\n EnrichmentResult,\n EntityEnrichmentInput,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Rate-limit tracking\n// ---------------------------------------------------------------------------\n\ninterface RateLimitBucket {\n minuteCount: number;\n minuteReset: number;\n dayCount: number;\n dayReset: number;\n}\n\nconst rateBuckets = new Map<string, RateLimitBucket>();\n\nfunction reserveRateLimitSlot(\n provider: EnrichmentProvider,\n config: EnrichmentPipelineConfig,\n): boolean {\n const providerCfg = config.providers.find((p) => p.id === provider.id);\n if (!providerCfg?.rateLimit) return true;\n\n const now = Date.now();\n let bucket = rateBuckets.get(provider.id);\n if (!bucket) {\n bucket = {\n minuteCount: 0,\n minuteReset: now + 60_000,\n dayCount: 0,\n dayReset: now + 86_400_000,\n };\n rateBuckets.set(provider.id, bucket);\n }\n\n // Reset windows if expired\n if (now >= bucket.minuteReset) {\n bucket.minuteCount = 0;\n bucket.minuteReset = now + 60_000;\n }\n if (now >= bucket.dayReset) {\n bucket.dayCount = 0;\n bucket.dayReset = now + 86_400_000;\n }\n\n const { maxPerMinute, maxPerDay } = providerCfg.rateLimit;\n if (bucket.minuteCount >= maxPerMinute || bucket.dayCount >= maxPerDay) {\n return false;\n }\n\n bucket.minuteCount += 1;\n bucket.dayCount += 1;\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline\n// ---------------------------------------------------------------------------\n\nexport async function runEnrichmentPipeline(\n entities: EntityEnrichmentInput[],\n registry: EnrichmentProviderRegistry,\n config: EnrichmentPipelineConfig,\n log: LoggerBackend,\n): Promise<EnrichmentResult[]> {\n if (!config.enabled) return [];\n if (entities.length === 0) return [];\n\n const results: EnrichmentResult[] = [];\n\n for (const entity of entities) {\n const providers = registry.getForImportance(entity.importanceLevel, config);\n const maxCandidates = config.maxCandidatesPerEntity;\n const hasPositiveCandidateBudget = maxCandidates > 0;\n let remainingCandidateBudget = hasPositiveCandidateBudget\n ? maxCandidates\n : Number.POSITIVE_INFINITY;\n\n for (const provider of providers) {\n if (hasPositiveCandidateBudget && remainingCandidateBudget <= 0) {\n break;\n }\n\n const start = Date.now();\n\n // Check availability\n let available: boolean;\n try {\n available = await provider.isAvailable();\n } catch {\n available = false;\n }\n\n if (!available) {\n log.debug?.(\n `enrichment: skipping provider ${provider.id} for ${entity.name} — unavailable`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n\n // Reserve quota before the awaited provider call so concurrent pipelines\n // cannot all pass the same pre-await rate-limit check.\n if (!reserveRateLimitSlot(provider, config)) {\n log.debug?.(\n `enrichment: skipping provider ${provider.id} for ${entity.name} — rate limited`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n\n // Run provider.\n // Count every attempt toward rate-limit buckets — including failures —\n // because the provider may have consumed external quota before throwing\n // (PR #425 review finding 2).\n let candidates: EnrichmentCandidate[];\n try {\n candidates = await provider.enrich(entity);\n } catch (err) {\n log.error?.(\n `enrichment: provider ${provider.id} failed for ${entity.name}: ${err instanceof Error ? err.message : String(err)}`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n\n // Tag each candidate with provider id\n for (const candidate of candidates) {\n candidate.source = provider.id;\n }\n\n // Cap at maxCandidatesPerEntity across all providers for this entity.\n // 0 means \"accept none\"; undefined/negative means \"no cap\".\n let accepted: EnrichmentCandidate[];\n if (maxCandidates === 0) {\n accepted = [];\n } else if (hasPositiveCandidateBudget) {\n accepted = candidates.slice(0, remainingCandidateBudget);\n remainingCandidateBudget -= accepted.length;\n } else {\n accepted = candidates;\n }\n const rejected = candidates.length - accepted.length;\n\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: candidates.length,\n candidatesAccepted: accepted.length,\n candidatesRejected: rejected,\n acceptedCandidates: accepted,\n elapsed: Date.now() - start,\n });\n }\n }\n\n return results;\n}\n","/**\n * Enrichment audit trail (issue #365).\n *\n * Append-only JSONL log for every enrichment candidate that was evaluated.\n * Each entry records whether the candidate was accepted or rejected, the\n * provider that produced it, and an optional reason string.\n */\n\nimport { mkdir, readFile, appendFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentAuditEntry {\n timestamp: string;\n entityName: string;\n provider: string;\n candidateText: string;\n sourceUrl?: string;\n accepted: boolean;\n reason?: string;\n}\n\n// ---------------------------------------------------------------------------\n// File helpers\n// ---------------------------------------------------------------------------\n\nconst AUDIT_FILENAME = \"enrichment-audit.jsonl\";\n\nfunction auditFilePath(auditDir: string): string {\n return path.join(auditDir, AUDIT_FILENAME);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Append a single audit entry to the JSONL log. Creates the audit directory\n * and file if they do not exist.\n */\nexport async function appendAuditEntry(\n auditDir: string,\n entry: EnrichmentAuditEntry,\n): Promise<void> {\n await mkdir(auditDir, { recursive: true });\n const line = JSON.stringify(entry) + \"\\n\";\n await appendFile(auditFilePath(auditDir), line, \"utf-8\");\n}\n\n/**\n * Read the audit log and return entries, optionally filtered to entries at\n * or after `since` (ISO 8601 timestamp, half-open interval).\n */\nexport async function readAuditLog(\n auditDir: string,\n since?: string,\n): Promise<EnrichmentAuditEntry[]> {\n const filePath = auditFilePath(auditDir);\n if (!existsSync(filePath)) return [];\n const sinceMs = since === undefined ? undefined : Date.parse(since);\n if (since !== undefined && !Number.isFinite(sinceMs)) {\n throw new Error(`Invalid enrichment audit since timestamp: ${since}`);\n }\n\n const raw = await readFile(filePath, \"utf-8\");\n const entries: EnrichmentAuditEntry[] = [];\n\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.length === 0) continue;\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"timestamp\" in parsed &&\n \"entityName\" in parsed\n ) {\n const entry = parsed as EnrichmentAuditEntry;\n if (typeof entry.timestamp !== \"string\") continue;\n const entryMs = Date.parse(entry.timestamp);\n if (!Number.isFinite(entryMs)) continue;\n if (sinceMs !== undefined && entryMs < sinceMs) continue;\n entries.push(entry);\n }\n } catch {\n // Skip malformed lines\n }\n }\n\n return entries;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readUtilityLearningSnapshot
3
- } from "./chunk-3BP57I6J.js";
3
+ } from "./chunk-2F6NP3NT.js";
4
4
  import {
5
5
  clamp01
6
6
  } from "./chunk-TBBDFYXW.js";
@@ -65,4 +65,4 @@ export {
65
65
  applyUtilityRankingRuntimeDelta,
66
66
  applyUtilityPromotionRuntimePolicy
67
67
  };
68
- //# sourceMappingURL=chunk-W7L6HXUC.js.map
68
+ //# sourceMappingURL=chunk-LXOM6IQU.js.map
@@ -225,6 +225,11 @@ function removePathFromSet(record, key, p) {
225
225
  delete record[key];
226
226
  }
227
227
  }
228
+ function removePathFromAllSets(record, p) {
229
+ for (const key of Object.keys(record)) {
230
+ removePathFromSet(record, key, p);
231
+ }
232
+ }
228
233
  function normalizeTagSegment(segment) {
229
234
  return segment.trim().toLowerCase().replace(/[_\s]+/g, "-").replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
230
235
  }
@@ -363,6 +368,38 @@ function removeTagGraphEntry(index, rawTag, memoryPath) {
363
368
  node.paths = node.paths.filter((value) => value !== memoryPath);
364
369
  if (node.paths.length === 0) {
365
370
  delete index.tags[canonical];
371
+ pruneTagAliases(index);
372
+ }
373
+ }
374
+ function removePathFromAllTagEntries(index, memoryPath) {
375
+ for (const [canonical, nodeOrPaths] of Object.entries(index.tags)) {
376
+ if (Array.isArray(nodeOrPaths)) {
377
+ const paths = nodeOrPaths.filter((value) => value !== memoryPath);
378
+ if (paths.length === 0) {
379
+ delete index.tags[canonical];
380
+ } else {
381
+ index.tags[canonical] = paths;
382
+ }
383
+ continue;
384
+ }
385
+ nodeOrPaths.paths = nodeOrPaths.paths.filter((value) => value !== memoryPath);
386
+ if (nodeOrPaths.paths.length === 0) {
387
+ delete index.tags[canonical];
388
+ }
389
+ }
390
+ pruneTagAliases(index);
391
+ }
392
+ function pruneTagAliases(index) {
393
+ if (!index.aliases) return;
394
+ for (const [alias, canonicals] of Object.entries(index.aliases)) {
395
+ const filtered = [...new Set(canonicals.map(normalizeCanonicalTag))].filter(
396
+ (canonical) => canonical.length > 0 && index.tags[canonical] !== void 0
397
+ );
398
+ if (filtered.length === 0) {
399
+ delete index.aliases[alias];
400
+ } else {
401
+ index.aliases[alias] = filtered;
402
+ }
366
403
  }
367
404
  }
368
405
  function expandCanonicalTags(index, rawTags) {
@@ -403,9 +440,11 @@ function indexMemory(memoryDir, memoryPath, createdAt, tags) {
403
440
  ensureStateDir(memoryDir);
404
441
  const dateKey = isoDateFromTimestamp(createdAt);
405
442
  updateTemporalIndex(memoryDir, (index) => {
443
+ removePathFromAllSets(index.dates, memoryPath);
406
444
  addPathToSet(index.dates, dateKey, memoryPath);
407
445
  });
408
446
  updateTagIndex(memoryDir, (index) => {
447
+ removePathFromAllTagEntries(index, memoryPath);
409
448
  for (const tag of tags) {
410
449
  if (tag && typeof tag === "string") {
411
450
  addTagGraphEntry(index, tag, memoryPath);
@@ -463,11 +502,13 @@ function indexMemoriesBatch(memoryDir, entries) {
463
502
  updateTemporalIndex(memoryDir, (index) => {
464
503
  for (const entry of entries) {
465
504
  const dateKey = isoDateFromTimestamp(entry.createdAt);
505
+ removePathFromAllSets(index.dates, entry.path);
466
506
  addPathToSet(index.dates, dateKey, entry.path);
467
507
  }
468
508
  });
469
509
  updateTagIndex(memoryDir, (index) => {
470
510
  for (const entry of entries) {
511
+ removePathFromAllTagEntries(index, entry.path);
471
512
  for (const tag of entry.tags) {
472
513
  if (tag && typeof tag === "string") {
473
514
  addTagGraphEntry(index, tag, entry.path);
@@ -795,4 +836,4 @@ export {
795
836
  recencyWindowFromPrompt,
796
837
  recencyWindowBoundsFromPrompt
797
838
  };
798
- //# sourceMappingURL=chunk-G6R5UD3Q.js.map
839
+ //# sourceMappingURL=chunk-MGN7VHWQ.js.map