@remnic/core 10.0.92 → 10.0.94

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 (501) hide show
  1. package/dist/access-admin-ops-surface.js +332 -59
  2. package/dist/access-authorization-probe.js +2 -283
  3. package/dist/access-boundary.d.ts +1 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +1 -282
  6. package/dist/access-cli.js +287 -303
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-coding-context-resolution.js +3 -3
  9. package/dist/access-extraction-force-flush.js +332 -59
  10. package/dist/access-http-briefing.js +332 -60
  11. package/dist/access-http-query.js +332 -61
  12. package/dist/access-http.js +330 -112
  13. package/dist/access-identity-continuity-surface.js +332 -59
  14. package/dist/access-lcm-surface.js +332 -59
  15. package/dist/access-mcp-frames.d.ts +20 -6
  16. package/dist/access-mcp-frames.d.ts.map +1 -1
  17. package/dist/access-mcp-frames.js +1 -1
  18. package/dist/access-mcp.d.ts +5 -0
  19. package/dist/access-mcp.d.ts.map +1 -1
  20. package/dist/access-mcp.js +330 -87
  21. package/dist/access-namespace-preflight.js +2 -282
  22. package/dist/access-observe-transcript.js +3 -3
  23. package/dist/access-observe-write-category.js +6 -6
  24. package/dist/access-observe-write-surface.js +332 -59
  25. package/dist/access-offline-manifest.js +12 -11
  26. package/dist/access-operations-batch.js +330 -64
  27. package/dist/access-operations.js +330 -73
  28. package/dist/access-recall-concurrency.js +332 -59
  29. package/dist/access-recall-diagnostics.js +5 -4
  30. package/dist/access-recall-response.js +332 -59
  31. package/dist/access-recall-surface.js +332 -59
  32. package/dist/access-schema.js +2 -2
  33. package/dist/access-service.js +335 -62
  34. package/dist/access-surface-catalog.js +6 -283
  35. package/dist/access-surface-catalog.js.map +1 -1
  36. package/dist/active-context-transform.js +1 -1
  37. package/dist/active-memory-bridge.js +3 -2
  38. package/dist/active-recall.js +36 -37
  39. package/dist/active-recall.js.map +1 -1
  40. package/dist/activity/journal-extract.d.ts +13 -1
  41. package/dist/activity/journal-extract.d.ts.map +1 -1
  42. package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
  43. package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
  44. package/dist/briefing.js +20 -20
  45. package/dist/buffer.js +16 -16
  46. package/dist/calibration.js +10 -10
  47. package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
  48. package/dist/causal-consolidation.js +23 -23
  49. package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
  50. package/dist/{chunk-BQZEGOZ4.js → chunk-2YODOG2O.js} +2 -2
  51. package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
  52. package/dist/{chunk-MB6ZJDN6.js → chunk-3FBAEPCU.js} +2 -2
  53. package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
  54. package/dist/{chunk-ATPWZ4F7.js → chunk-4EZWSDFL.js} +5 -5
  55. package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
  56. package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
  57. package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
  58. package/dist/{chunk-LUJCQQ3L.js → chunk-65HWAQ4F.js} +2 -2
  59. package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
  60. package/dist/chunk-A2WFUMUN.js +86 -0
  61. package/dist/chunk-A2WFUMUN.js.map +1 -0
  62. package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
  63. package/dist/chunk-AHA5VQGP.js +117 -0
  64. package/dist/chunk-AHA5VQGP.js.map +1 -0
  65. package/dist/{chunk-RRJBDXAQ.js → chunk-B377OKUP.js} +2 -2
  66. package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
  67. package/dist/chunk-B3DR2JBZ.js.map +1 -0
  68. package/dist/{chunk-Z7JIZ42P.js → chunk-BN2EBCLB.js} +3 -3
  69. package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
  70. package/dist/{chunk-U6UNF5ZS.js → chunk-BPK32FRM.js} +3 -3
  71. package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
  72. package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
  73. package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
  74. package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
  75. package/dist/{chunk-HF3BKUVV.js → chunk-CL4JU3DF.js} +7 -7
  76. package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
  77. package/dist/chunk-D5S6RL32.js +67137 -0
  78. package/dist/chunk-D5S6RL32.js.map +1 -0
  79. package/dist/{chunk-ETZLQOUR.js → chunk-D5XG2KPS.js} +2 -2
  80. package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
  81. package/dist/{chunk-PWXPRVLK.js → chunk-DNY63QWJ.js} +2 -2
  82. package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
  83. package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
  84. package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
  85. package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
  86. package/dist/chunk-FA7SLQ5X.js.map +1 -0
  87. package/dist/{chunk-BRLXOZLM.js → chunk-FQAQ63QA.js} +729 -17
  88. package/dist/chunk-FQAQ63QA.js.map +1 -0
  89. package/dist/{chunk-FSO3G4JJ.js → chunk-FWRXW7CZ.js} +5000 -2748
  90. package/dist/chunk-FWRXW7CZ.js.map +1 -0
  91. package/dist/{chunk-XYKBWTWI.js → chunk-FZZMLGWQ.js} +2 -2
  92. package/dist/{chunk-FNXQZGUS.js → chunk-GBJUTX44.js} +2 -2
  93. package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
  94. package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
  95. package/dist/chunk-HLUHEMN4.js.map +1 -0
  96. package/dist/{chunk-SUSW74WU.js → chunk-IL3RHBWM.js} +56 -5
  97. package/dist/chunk-IL3RHBWM.js.map +1 -0
  98. package/dist/{chunk-JV4S6VDH.js → chunk-ILY5SSOM.js} +2 -2
  99. package/dist/{chunk-EXHXJETE.js → chunk-IQ55QGY5.js} +2 -2
  100. package/dist/{chunk-4J363OAA.js → chunk-J7BIFOYP.js} +2 -2
  101. package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
  102. package/dist/chunk-JTSSSR4L.js +359 -0
  103. package/dist/chunk-JTSSSR4L.js.map +1 -0
  104. package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
  105. package/dist/chunk-JVBAPAWZ.js.map +1 -0
  106. package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
  107. package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
  108. package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
  109. package/dist/chunk-LLPMMMWG.js.map +1 -0
  110. package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
  111. package/dist/{chunk-E7YZXWIS.js → chunk-LNGELD2M.js} +2 -2
  112. package/dist/{chunk-FQKBJJYB.js → chunk-LPV72KRQ.js} +2 -2
  113. package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
  114. package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
  115. package/dist/chunk-MLAMXQAW.js.map +1 -0
  116. package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
  117. package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
  118. package/dist/{chunk-6F5MJFXY.js → chunk-NHVAX7MH.js} +2 -2
  119. package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
  120. package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
  121. package/dist/chunk-NS7U4ZYU.js.map +1 -0
  122. package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
  123. package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
  124. package/dist/{chunk-RIMK327Y.js → chunk-OUVYYKYU.js} +7 -7
  125. package/dist/{chunk-QJPJXCS4.js → chunk-P3WRNWWM.js} +157 -4
  126. package/dist/chunk-P3WRNWWM.js.map +1 -0
  127. package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
  128. package/dist/chunk-PC5DRBGD.js.map +1 -0
  129. package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
  130. package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
  131. package/dist/{chunk-MXHMNKWG.js → chunk-QDESY76F.js} +3 -3
  132. package/dist/{chunk-E3A7ELSG.js → chunk-QHARXWTC.js} +2 -2
  133. package/dist/{chunk-BLJT6JP2.js → chunk-QMFAMOT2.js} +2 -2
  134. package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
  135. package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
  136. package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
  137. package/dist/{chunk-RHQLIHMB.js → chunk-R4BBGPI2.js} +218 -218
  138. package/dist/chunk-R4BBGPI2.js.map +1 -0
  139. package/dist/{chunk-NT7F7WJQ.js → chunk-RFUL2ZIQ.js} +291 -93
  140. package/dist/chunk-RFUL2ZIQ.js.map +1 -0
  141. package/dist/{chunk-E4IRW2FG.js → chunk-RKABLU5M.js} +233 -40
  142. package/dist/chunk-RKABLU5M.js.map +1 -0
  143. package/dist/{chunk-P4WNBSTW.js → chunk-RNG7S3FY.js} +2 -2
  144. package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
  145. package/dist/chunk-RQFXZYTF.js.map +1 -0
  146. package/dist/{chunk-5HFXGXE5.js → chunk-RUTN5MMC.js} +7 -9
  147. package/dist/{chunk-5HFXGXE5.js.map → chunk-RUTN5MMC.js.map} +1 -1
  148. package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
  149. package/dist/chunk-S3S72HRM.js.map +1 -0
  150. package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
  151. package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
  152. package/dist/{chunk-PMJFRDJI.js → chunk-SUTEBYGI.js} +2 -2
  153. package/dist/{chunk-HVX4LPNY.js → chunk-T4KY6UYF.js} +5 -3
  154. package/dist/{chunk-HVX4LPNY.js.map → chunk-T4KY6UYF.js.map} +1 -1
  155. package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
  156. package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
  157. package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
  158. package/dist/{chunk-B4QYQFGY.js → chunk-UBEKET2V.js} +383 -36
  159. package/dist/chunk-UBEKET2V.js.map +1 -0
  160. package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
  161. package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
  162. package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
  163. package/dist/{chunk-NBH6K6G5.js → chunk-V65H7MI6.js} +345 -16
  164. package/dist/chunk-V65H7MI6.js.map +1 -0
  165. package/dist/chunk-W54Z2MOP.js +4500 -0
  166. package/dist/chunk-W54Z2MOP.js.map +1 -0
  167. package/dist/{chunk-7W3RXOIG.js → chunk-WGGRZYSQ.js} +14 -14
  168. package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
  169. package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
  170. package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
  171. package/dist/{chunk-6F42QONS.js → chunk-X5HGU2EM.js} +4 -4
  172. package/dist/{chunk-GVGAO3WV.js → chunk-XJ2S5UBF.js} +3 -3
  173. package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
  174. package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
  175. package/dist/chunk-XRWWCW4H.js +102 -0
  176. package/dist/chunk-XRWWCW4H.js.map +1 -0
  177. package/dist/{chunk-HH5LY6RR.js → chunk-YIXJSW5M.js} +5 -5
  178. package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
  179. package/dist/{chunk-N4KAE2NB.js → chunk-Z3FTSHT5.js} +3 -3
  180. package/dist/{chunk-7GLJMDEQ.js → chunk-ZDV5YWID.js} +6 -6
  181. package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
  182. package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
  183. package/dist/cli.js +303 -185
  184. package/dist/coding/export-okf-codegraph.js +29 -29
  185. package/dist/compounding/engine.js +23 -24
  186. package/dist/config.js +36 -37
  187. package/dist/connectors/codex-materialize-runner.js +20 -20
  188. package/dist/connectors/index.js +21 -21
  189. package/dist/connectors/live/google-drive.d.ts +2 -0
  190. package/dist/connectors/live/google-drive.d.ts.map +1 -1
  191. package/dist/consolidation-provenance-check.js +4 -4
  192. package/dist/consolidation-undo.js +5 -5
  193. package/dist/contradiction/index.js +7 -6
  194. package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
  195. package/dist/convergence-refresh.js +22 -22
  196. package/dist/corpus-watermark.js +23 -23
  197. package/dist/deep-recall-http-glue.js +2 -2
  198. package/dist/deep-recall-policy-llm.js +4 -4
  199. package/dist/direct-answer-wiring.js +2 -2
  200. package/dist/embedding-fallback.js +1 -1
  201. package/dist/entity-retrieval.js +21 -21
  202. package/dist/eval-trajectory-store.js +2 -2
  203. package/dist/eval-trajectory.js +2 -2
  204. package/dist/event-order-recall.js +2 -2
  205. package/dist/explicit-capture.d.ts +3 -2
  206. package/dist/explicit-capture.d.ts.map +1 -1
  207. package/dist/explicit-capture.js +530 -6
  208. package/dist/explicit-cue-recall.js +2 -2
  209. package/dist/external-wiki-access.js +2 -283
  210. package/dist/extraction-faithfulness.js +4 -4
  211. package/dist/extraction-judge.js +5 -5
  212. package/dist/extraction-normalization.js +1 -1
  213. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  214. package/dist/extraction-quote-grounding.js +1 -1
  215. package/dist/extraction-source-grounding-rules.js +2 -2
  216. package/dist/extraction-source-grounding.js +3 -3
  217. package/dist/extraction.js +43 -44
  218. package/dist/fallback-llm-codex-subscription.js +2 -2
  219. package/dist/fallback-llm.js +3 -3
  220. package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
  221. package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
  222. package/dist/focused-list-recall.js +2 -2
  223. package/dist/harmonic-retrieval.js +20 -20
  224. package/dist/index.js +773 -794
  225. package/dist/lcm/index.js +2 -2
  226. package/dist/live-connectors-runner.js +2 -3
  227. package/dist/local-llm-config.js +2 -2
  228. package/dist/local-llm-helpers.d.ts +4 -0
  229. package/dist/local-llm-helpers.d.ts.map +1 -1
  230. package/dist/local-llm-helpers.js +3 -1
  231. package/dist/local-llm.d.ts +4 -1
  232. package/dist/local-llm.d.ts.map +1 -1
  233. package/dist/local-llm.js +4 -4
  234. package/dist/location/index.js +27 -29
  235. package/dist/maintenance/memory-governance.js +20 -20
  236. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -20
  237. package/dist/maintenance/rebuild-memory-projection-runner.js +22 -22
  238. package/dist/maintenance/rebuild-memory-projection.js +21 -21
  239. package/dist/memory-browse-operations.js +2 -283
  240. package/dist/memory-browse.js +8 -7
  241. package/dist/memory-controller.js +2 -2
  242. package/dist/memory-subject.js +2 -2
  243. package/dist/namespaces/migrate.js +21 -21
  244. package/dist/namespaces/search.js +5 -5
  245. package/dist/namespaces/storage.js +20 -20
  246. package/dist/offline-sync-changeset-generations.js +1 -1
  247. package/dist/offline-sync-embedding-generation.js +1 -1
  248. package/dist/offline-sync-impression-drain.js +6 -6
  249. package/dist/offline-sync-snapshot-stream.js +1 -1
  250. package/dist/offline-sync.js +1 -1
  251. package/dist/operator-doctor-corpus.js +24 -24
  252. package/dist/operator-toolkit.js +58 -61
  253. package/dist/orchestration/backlog-scheduler.js +278 -294
  254. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  255. package/dist/orchestration/buffer-drain-runner.js +278 -294
  256. package/dist/orchestration/extraction-run.d.ts +4 -0
  257. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  258. package/dist/orchestration/fact-dedup-window.d.ts +1 -1
  259. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  260. package/dist/orchestration/maintenance.js +39 -42
  261. package/dist/orchestrator.d.ts +9 -11
  262. package/dist/orchestrator.d.ts.map +1 -1
  263. package/dist/orchestrator.js +290 -307
  264. package/dist/page-versioning.js +3 -3
  265. package/dist/qmd-daemon-session.js +2 -2
  266. package/dist/qmd.js +3 -3
  267. package/dist/recall-explain-renderer.js +1 -1
  268. package/dist/recall-navigation-operations.js +2 -283
  269. package/dist/recall-navigation.js +332 -59
  270. package/dist/recall-planner-llm.js +3 -3
  271. package/dist/recall-state-view-admission.js +6 -6
  272. package/dist/recall-why-service.js +10 -9
  273. package/dist/recall-why.js +4 -3
  274. package/dist/recall-xray-cli.js +2 -2
  275. package/dist/recall-xray-renderer.js +1 -1
  276. package/dist/reconcile/manifest.js +3 -3
  277. package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
  278. package/dist/response-guidance-recall.js +2 -2
  279. package/dist/resume-bundles.js +40 -41
  280. package/dist/salvage-envelope.js +4 -4
  281. package/dist/search/factory.js +4 -4
  282. package/dist/search/index.js +4 -4
  283. package/dist/semantic-consolidation.js +23 -23
  284. package/dist/semantic-rule-promotion.js +20 -20
  285. package/dist/semantic-rule-verifier.js +20 -20
  286. package/dist/shared-context/manager.js +2 -3
  287. package/dist/source-agent-qualifier.js +32 -33
  288. package/dist/standup/build.js +20 -20
  289. package/dist/storage/buffer-drain-claims.d.ts +97 -12
  290. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  291. package/dist/storage/buffer-drain-claims.js +1 -1
  292. package/dist/storage/buffer-session-files.d.ts +4 -0
  293. package/dist/storage/buffer-session-files.d.ts.map +1 -1
  294. package/dist/storage/buffer-session-files.js +1 -1
  295. package/dist/storage/content-hash-index.d.ts +10 -0
  296. package/dist/storage/content-hash-index.d.ts.map +1 -1
  297. package/dist/storage.d.ts.map +1 -1
  298. package/dist/storage.js +19 -19
  299. package/dist/subject-config.js +3 -3
  300. package/dist/summarizer.d.ts +4 -1
  301. package/dist/summarizer.d.ts.map +1 -1
  302. package/dist/summarizer.js +6 -6
  303. package/dist/support-passport/index.js +53 -297
  304. package/dist/targeted-fact-recall.js +2 -2
  305. package/dist/tier-routing.js +3 -2
  306. package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
  307. package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
  308. package/dist/transcript.js +2 -2
  309. package/dist/transfer/capsule-import.js +4 -4
  310. package/dist/transfer/export-okf.js +20 -20
  311. package/dist/verified-recall.js +20 -20
  312. package/dist/wearables/memory-gen.d.ts +5 -1
  313. package/dist/wearables/memory-gen.d.ts.map +1 -1
  314. package/dist/wearables/service.d.ts.map +1 -1
  315. package/dist/who-knows-cli.js +21 -21
  316. package/dist/who-knows.js +20 -20
  317. package/dist/write-envelope.js +3 -3
  318. package/package.json +2 -2
  319. package/src/access-boundary.ts +5 -1
  320. package/src/access-category-alias.test.ts +5 -0
  321. package/src/access-mcp-frames.test.ts +60 -5
  322. package/src/access-mcp-frames.ts +72 -18
  323. package/src/access-mcp.test.ts +66 -2
  324. package/src/access-mcp.ts +19 -13
  325. package/src/access-service-coding-write.test.ts +7 -0
  326. package/src/activity/journal-extract-storage.test.ts +31 -0
  327. package/src/activity/journal-extract.test.ts +1 -0
  328. package/src/activity/journal-extract.ts +24 -3
  329. package/src/buffer-drain-handover.test.ts +5 -1
  330. package/src/connectors/live/google-drive.test.ts +39 -0
  331. package/src/connectors/live/google-drive.ts +41 -12
  332. package/src/explicit-capture.test.ts +109 -17
  333. package/src/explicit-capture.ts +90 -37
  334. package/src/extraction-quote-grounding.test.ts +20 -0
  335. package/src/extraction-quote-grounding.ts +21 -11
  336. package/src/fact-dedup-window.test.ts +37 -0
  337. package/src/local-llm-helpers.ts +12 -0
  338. package/src/local-llm-shutdown.test.ts +194 -0
  339. package/src/local-llm.ts +32 -30
  340. package/src/orchestration/extraction-run.ts +12 -3
  341. package/src/orchestration/fact-dedup-window.ts +5 -1
  342. package/src/orchestrator-connector-dedup.test.ts +9 -5
  343. package/src/orchestrator.ts +47 -50
  344. package/src/storage/buffer-drain-claims.test.ts +385 -1
  345. package/src/storage/buffer-drain-claims.ts +380 -97
  346. package/src/storage/buffer-session-files.ts +4 -0
  347. package/src/storage/content-hash-index.ts +27 -2
  348. package/src/storage-fact-hash-publish-failure.test.ts +87 -0
  349. package/src/storage.ts +5 -1
  350. package/src/summarizer-shutdown.test.ts +195 -0
  351. package/src/summarizer.ts +15 -2
  352. package/src/wearables/memory-gen.test.ts +20 -0
  353. package/src/wearables/memory-gen.ts +14 -7
  354. package/src/wearables/service.ts +14 -1
  355. package/src/wearables/storage-io.test.ts +25 -0
  356. package/dist/chunk-2FGLEF7C.js +0 -61
  357. package/dist/chunk-2FGLEF7C.js.map +0 -1
  358. package/dist/chunk-3RLK65ZK.js +0 -203
  359. package/dist/chunk-3RLK65ZK.js.map +0 -1
  360. package/dist/chunk-4WR2OD7K.js +0 -43
  361. package/dist/chunk-4WR2OD7K.js.map +0 -1
  362. package/dist/chunk-5KBIOI4G.js.map +0 -1
  363. package/dist/chunk-5M5M22GW.js +0 -346
  364. package/dist/chunk-5M5M22GW.js.map +0 -1
  365. package/dist/chunk-7HTUYFR3.js.map +0 -1
  366. package/dist/chunk-AP6NCP3V.js.map +0 -1
  367. package/dist/chunk-B4QYQFGY.js.map +0 -1
  368. package/dist/chunk-BJRTMNCB.js +0 -1919
  369. package/dist/chunk-BJRTMNCB.js.map +0 -1
  370. package/dist/chunk-BRLXOZLM.js.map +0 -1
  371. package/dist/chunk-BSACVS6R.js +0 -621
  372. package/dist/chunk-BSACVS6R.js.map +0 -1
  373. package/dist/chunk-DKAA2IHQ.js +0 -3644
  374. package/dist/chunk-DKAA2IHQ.js.map +0 -1
  375. package/dist/chunk-E4IRW2FG.js.map +0 -1
  376. package/dist/chunk-E6M225BJ.js.map +0 -1
  377. package/dist/chunk-EQ4U73UH.js.map +0 -1
  378. package/dist/chunk-ETSJD5IV.js +0 -361
  379. package/dist/chunk-ETSJD5IV.js.map +0 -1
  380. package/dist/chunk-F55YQP7N.js +0 -336
  381. package/dist/chunk-F55YQP7N.js.map +0 -1
  382. package/dist/chunk-FHGWKODN.js +0 -2300
  383. package/dist/chunk-FHGWKODN.js.map +0 -1
  384. package/dist/chunk-FLRP63GU.js +0 -32219
  385. package/dist/chunk-FLRP63GU.js.map +0 -1
  386. package/dist/chunk-FSO3G4JJ.js.map +0 -1
  387. package/dist/chunk-ID6JGIMD.js +0 -25
  388. package/dist/chunk-ID6JGIMD.js.map +0 -1
  389. package/dist/chunk-IGLJNHOX.js +0 -62
  390. package/dist/chunk-IGLJNHOX.js.map +0 -1
  391. package/dist/chunk-IPT2OY4B.js.map +0 -1
  392. package/dist/chunk-IUKYFTGW.js.map +0 -1
  393. package/dist/chunk-NBH6K6G5.js.map +0 -1
  394. package/dist/chunk-NT7F7WJQ.js.map +0 -1
  395. package/dist/chunk-NTRUKAAE.js +0 -223
  396. package/dist/chunk-NTRUKAAE.js.map +0 -1
  397. package/dist/chunk-OGXXGHW6.js +0 -223
  398. package/dist/chunk-OGXXGHW6.js.map +0 -1
  399. package/dist/chunk-PPCLZ4JU.js +0 -28
  400. package/dist/chunk-PPCLZ4JU.js.map +0 -1
  401. package/dist/chunk-QJPJXCS4.js.map +0 -1
  402. package/dist/chunk-R2LBF4HH.js +0 -729
  403. package/dist/chunk-R2LBF4HH.js.map +0 -1
  404. package/dist/chunk-RHQLIHMB.js.map +0 -1
  405. package/dist/chunk-RRTVGD3J.js +0 -792
  406. package/dist/chunk-RRTVGD3J.js.map +0 -1
  407. package/dist/chunk-SUSW74WU.js.map +0 -1
  408. package/dist/chunk-T6OZW2WR.js.map +0 -1
  409. package/dist/chunk-UDKWFDV2.js.map +0 -1
  410. package/dist/chunk-V7IZKJI3.js +0 -22
  411. package/dist/chunk-V7IZKJI3.js.map +0 -1
  412. package/dist/chunk-X562NQFA.js +0 -10144
  413. package/dist/chunk-X562NQFA.js.map +0 -1
  414. package/dist/chunk-XDSCJOH7.js +0 -3991
  415. package/dist/chunk-XDSCJOH7.js.map +0 -1
  416. package/dist/chunk-YCNIGRRF.js.map +0 -1
  417. package/dist/chunk-ZTTCDQ4I.js +0 -18796
  418. package/dist/chunk-ZTTCDQ4I.js.map +0 -1
  419. /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
  420. /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
  421. /package/dist/{chunk-BQZEGOZ4.js.map → chunk-2YODOG2O.js.map} +0 -0
  422. /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
  423. /package/dist/{chunk-MB6ZJDN6.js.map → chunk-3FBAEPCU.js.map} +0 -0
  424. /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
  425. /package/dist/{chunk-ATPWZ4F7.js.map → chunk-4EZWSDFL.js.map} +0 -0
  426. /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
  427. /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
  428. /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
  429. /package/dist/{chunk-LUJCQQ3L.js.map → chunk-65HWAQ4F.js.map} +0 -0
  430. /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
  431. /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
  432. /package/dist/{chunk-RRJBDXAQ.js.map → chunk-B377OKUP.js.map} +0 -0
  433. /package/dist/{chunk-Z7JIZ42P.js.map → chunk-BN2EBCLB.js.map} +0 -0
  434. /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
  435. /package/dist/{chunk-U6UNF5ZS.js.map → chunk-BPK32FRM.js.map} +0 -0
  436. /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
  437. /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
  438. /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
  439. /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
  440. /package/dist/{chunk-HF3BKUVV.js.map → chunk-CL4JU3DF.js.map} +0 -0
  441. /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
  442. /package/dist/{chunk-ETZLQOUR.js.map → chunk-D5XG2KPS.js.map} +0 -0
  443. /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
  444. /package/dist/{chunk-PWXPRVLK.js.map → chunk-DNY63QWJ.js.map} +0 -0
  445. /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
  446. /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
  447. /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
  448. /package/dist/{chunk-XYKBWTWI.js.map → chunk-FZZMLGWQ.js.map} +0 -0
  449. /package/dist/{chunk-FNXQZGUS.js.map → chunk-GBJUTX44.js.map} +0 -0
  450. /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
  451. /package/dist/{chunk-JV4S6VDH.js.map → chunk-ILY5SSOM.js.map} +0 -0
  452. /package/dist/{chunk-EXHXJETE.js.map → chunk-IQ55QGY5.js.map} +0 -0
  453. /package/dist/{chunk-4J363OAA.js.map → chunk-J7BIFOYP.js.map} +0 -0
  454. /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
  455. /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
  456. /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
  457. /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
  458. /package/dist/{chunk-E7YZXWIS.js.map → chunk-LNGELD2M.js.map} +0 -0
  459. /package/dist/{chunk-FQKBJJYB.js.map → chunk-LPV72KRQ.js.map} +0 -0
  460. /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
  461. /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
  462. /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
  463. /package/dist/{chunk-6F5MJFXY.js.map → chunk-NHVAX7MH.js.map} +0 -0
  464. /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
  465. /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
  466. /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
  467. /package/dist/{chunk-RIMK327Y.js.map → chunk-OUVYYKYU.js.map} +0 -0
  468. /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
  469. /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
  470. /package/dist/{chunk-MXHMNKWG.js.map → chunk-QDESY76F.js.map} +0 -0
  471. /package/dist/{chunk-E3A7ELSG.js.map → chunk-QHARXWTC.js.map} +0 -0
  472. /package/dist/{chunk-BLJT6JP2.js.map → chunk-QMFAMOT2.js.map} +0 -0
  473. /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
  474. /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
  475. /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
  476. /package/dist/{chunk-P4WNBSTW.js.map → chunk-RNG7S3FY.js.map} +0 -0
  477. /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
  478. /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
  479. /package/dist/{chunk-PMJFRDJI.js.map → chunk-SUTEBYGI.js.map} +0 -0
  480. /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
  481. /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
  482. /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
  483. /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
  484. /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
  485. /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
  486. /package/dist/{chunk-7W3RXOIG.js.map → chunk-WGGRZYSQ.js.map} +0 -0
  487. /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
  488. /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
  489. /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
  490. /package/dist/{chunk-6F42QONS.js.map → chunk-X5HGU2EM.js.map} +0 -0
  491. /package/dist/{chunk-GVGAO3WV.js.map → chunk-XJ2S5UBF.js.map} +0 -0
  492. /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
  493. /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
  494. /package/dist/{chunk-HH5LY6RR.js.map → chunk-YIXJSW5M.js.map} +0 -0
  495. /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
  496. /package/dist/{chunk-N4KAE2NB.js.map → chunk-Z3FTSHT5.js.map} +0 -0
  497. /package/dist/{chunk-7GLJMDEQ.js.map → chunk-ZDV5YWID.js.map} +0 -0
  498. /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
  499. /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
  500. /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
  501. /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
@@ -12,6 +12,10 @@ import type { BufferEntryState, BufferState, BufferTurn } from "../types.js";
12
12
  export interface BufferFileIo {
13
13
  read(filePath: string): Promise<string>;
14
14
  write(filePath: string, content: string): Promise<void>;
15
+ /** Durable append (drain-claim inboxes). Implementations must persist
16
+ * `content` before resolving; a whole-file read+concat+write rewrite is
17
+ * acceptable — inbox files stay small between drainer compactions. */
18
+ append(filePath: string, content: string): Promise<void>;
15
19
  }
16
20
 
17
21
  type FsError = NodeJS.ErrnoException;
@@ -3,7 +3,7 @@
3
3
  // rebuild saves plus the deferred durable-retry machinery. storage.ts imports
4
4
  // these back and re-exports the class + companion types that were previously
5
5
  // part of its public API.
6
- import { mkdir, stat } from "node:fs/promises";
6
+ import { mkdir, stat, utimes } from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  import { computeContentHash, normalizeContent } from "../content-hash.js";
9
9
  import { log } from "../logger.js";
@@ -291,6 +291,25 @@ export class ContentHashIndex {
291
291
  );
292
292
  }
293
293
 
294
+ /**
295
+ * Change the durable file's mtime without touching its content. A publish
296
+ * that throws or stays deferred leaves the file as it was, so a peer that
297
+ * already synced it keeps answering authoritative MISSes for deltas this
298
+ * instance holds only in memory (#3355). A new stamp makes every holder of the
299
+ * old fingerprint, this instance included, fail the freshness check and
300
+ * confirm against the corpus. Best effort: a file that cannot be stamped
301
+ * leaves the callers' existing retry and rebuild paths as the only repair.
302
+ */
303
+ private async invalidatePeerFreshness(): Promise<void> {
304
+ try {
305
+ const { mtimeMs } = await stat(this.filePath);
306
+ const bumped = new Date(Math.max(Date.now(), mtimeMs + 1));
307
+ await utimes(this.filePath, bumped, bumped);
308
+ } catch (err) {
309
+ log.debug(`content-hash index: could not invalidate peer freshness of ${this.filePath}: ${err}`);
310
+ }
311
+ }
312
+
294
313
  /** Check if content already exists in the index. */
295
314
  has(content: string): boolean {
296
315
  return this.hashes.has(ContentHashIndex.computeHash(content));
@@ -473,9 +492,15 @@ export class ContentHashIndex {
473
492
  `content-hash index: lock not acquired for ${this.filePath}; deferring reconcile save (dirty retained, scheduling durable retry)`
474
493
  );
475
494
  this.scheduleReconcileRetry();
495
+ await this.invalidatePeerFreshness();
476
496
  return;
477
497
  }
478
- await publish();
498
+ try {
499
+ await publish();
500
+ } catch (err) {
501
+ await this.invalidatePeerFreshness();
502
+ throw err;
503
+ }
479
504
  if (this.dirty) {
480
505
  // A concurrent add()/remove() landed during publish's disk awaits, so
481
506
  // deltas remain unpersisted (PR #2016 thread SD7Tj). Arm a bounded
@@ -0,0 +1,87 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { StorageManager } from "./storage.js";
7
+ import { ContentHashIndex } from "./storage/content-hash-index.js";
8
+
9
+ const FACT = "the quarterly budget was approved by the board";
10
+ const CONTENDED_LOCK_OPTIONS = { maxWaitMs: 40, pollMs: 10, retryMaxAttempts: 2, retryBaseMs: 10 };
11
+
12
+ async function withMemoryDir(run: (dir: string) => Promise<void>): Promise<void> {
13
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-fact-hash-publish-"));
14
+ try {
15
+ await run(dir);
16
+ } finally {
17
+ await rm(dir, { recursive: true, force: true });
18
+ }
19
+ }
20
+
21
+ /** Hold the fact-hash index lock the way a peer mid-publish does; returns the release. */
22
+ async function holdIndexLock(dir: string): Promise<() => Promise<void>> {
23
+ const lockPath = path.join(dir, "state", "fact-hashes.txt.lock");
24
+ await mkdir(path.dirname(lockPath), { recursive: true });
25
+ await writeFile(lockPath, `99999 peer-owner ${new Date().toISOString()}\n`);
26
+ return () => rm(lockPath, { force: true });
27
+ }
28
+
29
+ test("a publish that throws leaves peers revalidating instead of trusting the old file (#3355)", async () => {
30
+ await withMemoryDir(async (dir) => {
31
+ const stateDir = path.join(dir, "state");
32
+ const peer = new ContentHashIndex(stateDir);
33
+ peer.add("seed fact");
34
+ await peer.saveMergingWithDisk();
35
+ assert.equal(await peer.isDiskFingerprintCurrent(), true, "the peer starts in sync with the file");
36
+
37
+ const writer = new ContentHashIndex(
38
+ stateDir,
39
+ () => null,
40
+ () => {
41
+ throw new Error("write key unavailable");
42
+ },
43
+ );
44
+ await writer.load();
45
+ writer.add("pending fact");
46
+ await assert.rejects(writer.saveMergingWithDisk(), /write key unavailable/);
47
+
48
+ assert.equal(writer.hasPendingChanges, true, "the failed addition stays pending in the writer");
49
+ assert.equal(
50
+ await peer.isDiskFingerprintCurrent(),
51
+ false,
52
+ "the file did not change, yet the peer must stop trusting its snapshot",
53
+ );
54
+ });
55
+ });
56
+
57
+ test("a writeMemory whose publish stays deferred does not leave a peer answering an authoritative MISS (#3355)", async () => {
58
+ await withMemoryDir(async (dir) => {
59
+ const peer = new StorageManager(dir);
60
+ const writer = new StorageManager(dir);
61
+ peer.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
62
+ writer.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
63
+ assert.deepEqual(
64
+ await peer.lookupContentHash(FACT),
65
+ { present: false, authoritative: true },
66
+ "the peer starts with an authoritative, empty index",
67
+ );
68
+
69
+ const release = await holdIndexLock(dir);
70
+ const written = await writer.writeMemory("fact", FACT);
71
+ assert.ok(written.id, "the memory is durable, so the write still succeeds");
72
+
73
+ const duringContention = await peer.lookupContentHash(FACT);
74
+ assert.ok(
75
+ duringContention.present || !duringContention.authoritative,
76
+ `a peer must not report an authoritative MISS for a fact on disk: ${JSON.stringify(duringContention)}`,
77
+ );
78
+
79
+ await release();
80
+ assert.deepEqual(
81
+ await peer.lookupContentHash(FACT),
82
+ { present: true, authoritative: true },
83
+ "once the lock is free the peer rebuilds from the corpus and sees the fact",
84
+ );
85
+ await writer.lookupContentHash(FACT);
86
+ });
87
+ });
package/src/storage.ts CHANGED
@@ -5497,7 +5497,11 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
5497
5497
  // Buffer persistence: one file per session (storage/buffer-session-files.ts).
5498
5498
  private bufferFiles?: BufferSessionFiles;
5499
5499
  private bufferSessionFiles(): BufferSessionFiles {
5500
- const io = { read: (p: string) => this.readStorageSecureFile(p), write: (p: string, c: string) => this.writeStorageSecureFile(p, c) };
5500
+ const io = {
5501
+ read: (p: string) => this.readStorageSecureFile(p),
5502
+ write: (p: string, c: string) => this.writeStorageSecureFile(p, c),
5503
+ append: (p: string, c: string) => this.appendStorageSecureFile(p, c),
5504
+ };
5501
5505
  return (this.bufferFiles ??= new BufferSessionFiles(this.stateDir, io));
5502
5506
  }
5503
5507
  async loadBuffer(): Promise<BufferState> { return this.bufferSessionFiles().load(); }
@@ -0,0 +1,195 @@
1
+ // Shutdown-signal behavioral tests for HourlySummarizer: an aborted
2
+ // summarizer must abort its in-flight local LLM call (the abort reaches the
3
+ // client stub) and must refuse to start new summary work once aborted.
4
+ import assert from "node:assert/strict";
5
+ import { mkdtemp, rm } from "node:fs/promises";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import test from "node:test";
9
+
10
+ import { parseConfig } from "./config.js";
11
+ import { initLogger, resetLogger } from "./logger.js";
12
+ import { HourlySummarizer } from "./summarizer.js";
13
+ import type { LocalLlmChatCompletionOptions } from "./local-llm-helpers.js";
14
+ import type { TranscriptEntry } from "./types.js";
15
+
16
+ function createConfig(memoryDir: string, extended = false) {
17
+ return parseConfig({
18
+ openaiApiKey: "sk-test",
19
+ memoryDir,
20
+ workspaceDir: path.join(memoryDir, "workspace"),
21
+ localLlmEnabled: true,
22
+ localLlmModel: "test-local-model",
23
+ localLlmUrl: "http://127.0.0.1:9",
24
+ localLlmTimeoutMs: 600_000,
25
+ localLlmRetry5xxCount: 1,
26
+ localLlmRetryBackoffMs: 30_000,
27
+ hourlySummariesEnabled: true,
28
+ hourlySummariesExtendedEnabled: extended,
29
+ debug: false,
30
+ });
31
+ }
32
+
33
+ interface LlmStub {
34
+ calls: { signal: AbortSignal | undefined }[];
35
+ }
36
+
37
+ function stubLocalLlm(summarizer: HourlySummarizer, stub: LlmStub): void {
38
+ // SAFETY: localLlm is a private field, stubbed at the established field
39
+ // seam with the client's real call shape; the stub parks until its caller
40
+ // signal aborts (real transport behavior), mirroring a wedged local LLM.
41
+ (summarizer as any).localLlm = {
42
+ chatCompletion: async (
43
+ messages: Array<{ role: string; content: string }>,
44
+ options: LocalLlmChatCompletionOptions,
45
+ ): Promise<null> => {
46
+ void messages;
47
+ stub.calls.push({ signal: options.signal });
48
+ const { promise, reject } = Promise.withResolvers<null>();
49
+ const onAbort = (): void => {
50
+ reject(new DOMException("This operation was aborted", "AbortError"));
51
+ };
52
+ const signal = options.signal;
53
+ if (signal) {
54
+ if (signal.aborted) onAbort();
55
+ else signal.addEventListener("abort", onAbort, { once: true });
56
+ }
57
+ return await promise;
58
+ },
59
+ };
60
+ }
61
+
62
+ async function withTestDeadline<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
63
+ const { promise: deadline, reject } = Promise.withResolvers<never>();
64
+ const timer = setTimeout(() => reject(new Error(`${label}: test deadline exceeded (${timeoutMs}ms)`)), timeoutMs);
65
+ timer.unref?.();
66
+ try {
67
+ return await Promise.race([promise, deadline]);
68
+ } finally {
69
+ clearTimeout(timer);
70
+ }
71
+ }
72
+
73
+ function quietLogger(): void {
74
+ initLogger({ info() {}, warn() {}, error() {}, debug() {} }, false);
75
+ }
76
+
77
+ function entriesFor(): TranscriptEntry[] {
78
+ return [
79
+ {
80
+ role: "user",
81
+ content: "bounded shutdown summarizer test entry",
82
+ timestamp: new Date(Date.now() - 3_600_000).toISOString(),
83
+ sessionKey: "sess-summary-shutdown",
84
+ turnId: "turn-shutdown-1",
85
+ },
86
+ ];
87
+ }
88
+
89
+ test("aborting the shutdown signal aborts an in-flight generateSummary and reaches the LLM stub", async () => {
90
+ quietLogger();
91
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-inflight-"));
92
+ const stub: LlmStub = { calls: [] };
93
+ try {
94
+ const controller = new AbortController();
95
+ const summarizer = new HourlySummarizer(
96
+ createConfig(memoryDir),
97
+ undefined,
98
+ undefined,
99
+ undefined,
100
+ controller.signal,
101
+ );
102
+ stubLocalLlm(summarizer, stub);
103
+
104
+ const hourStart = new Date(Date.now() - 3_600_000);
105
+ hourStart.setMinutes(0, 0, 0);
106
+ const pending = summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor());
107
+
108
+ await withTestDeadline((async () => {
109
+ while (stub.calls.length === 0) {
110
+ await new Promise((resolve) => setTimeout(resolve, 10));
111
+ }
112
+ })(), 5_000, "LLM stub to be entered");
113
+ controller.abort();
114
+
115
+ const result = await withTestDeadline(pending, 2_000, "in-flight summarizer abort");
116
+ assert.equal(result, null);
117
+ assert.equal(stub.calls.length, 1, "no second LLM attempt may start after abort");
118
+ assert.equal(stub.calls[0]?.signal?.aborted, true, "the shutdown abort must reach the LLM stub");
119
+ } finally {
120
+ resetLogger();
121
+ await rm(memoryDir, { recursive: true, force: true });
122
+ }
123
+ });
124
+
125
+ test("an already-aborted summarizer refuses to start new work and never contacts the LLM", async () => {
126
+ quietLogger();
127
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-refuse-"));
128
+ const stub: LlmStub = { calls: [] };
129
+ try {
130
+ const controller = new AbortController();
131
+ controller.abort();
132
+ const summarizer = new HourlySummarizer(
133
+ createConfig(memoryDir),
134
+ undefined,
135
+ undefined,
136
+ undefined,
137
+ controller.signal,
138
+ );
139
+ stubLocalLlm(summarizer, stub);
140
+
141
+ const hourStart = new Date(Date.now() - 3_600_000);
142
+ hourStart.setMinutes(0, 0, 0);
143
+ await assert.rejects(
144
+ summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor()),
145
+ );
146
+ assert.equal(stub.calls.length, 0, "aborted summarizer must not start new LLM work");
147
+ } finally {
148
+ resetLogger();
149
+ await rm(memoryDir, { recursive: true, force: true });
150
+ }
151
+ });
152
+
153
+ for (const extended of [false, true]) {
154
+ test(`a shutdown abort during the local call does not start a gateway call (${extended ? "extended" : "standard"} summary)`, async () => {
155
+ quietLogger();
156
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-gateway-"));
157
+ const stub: LlmStub = { calls: [] };
158
+ let gatewayCalls = 0;
159
+ try {
160
+ const controller = new AbortController();
161
+ const summarizer = new HourlySummarizer(
162
+ createConfig(memoryDir, extended),
163
+ undefined,
164
+ undefined,
165
+ undefined,
166
+ controller.signal,
167
+ );
168
+ stubLocalLlm(summarizer, stub);
169
+ // SAFETY: fallbackLlm is a private field, stubbed at the same field seam
170
+ // as localLlm; the stub counts gateway calls and returns no result.
171
+ (summarizer as any).fallbackLlm = {
172
+ parseWithSchema: async (): Promise<null> => {
173
+ gatewayCalls += 1;
174
+ return null;
175
+ },
176
+ };
177
+
178
+ const hourStart = new Date(Date.now() - 3_600_000);
179
+ hourStart.setMinutes(0, 0, 0);
180
+ const pending = summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor());
181
+ await withTestDeadline((async () => {
182
+ while (stub.calls.length === 0) {
183
+ await new Promise((resolve) => setTimeout(resolve, 10));
184
+ }
185
+ })(), 5_000, "LLM stub to be entered");
186
+ controller.abort();
187
+
188
+ assert.equal(await withTestDeadline(pending, 2_000, "in-flight summarizer abort"), null);
189
+ assert.equal(gatewayCalls, 0, "no gateway call may start after the shutdown abort");
190
+ } finally {
191
+ resetLogger();
192
+ await rm(memoryDir, { recursive: true, force: true });
193
+ }
194
+ });
195
+ }
package/src/summarizer.ts CHANGED
@@ -114,20 +114,25 @@ export class HourlySummarizer {
114
114
  private fallbackLlm: FallbackLlmClient;
115
115
  private modelRegistry: ModelRegistry;
116
116
  private transcript?: TranscriptManager;
117
+ /** Orchestrator-owned stop signal: refuses new summaries and aborts the
118
+ * in-flight local LLM call once teardown begins. */
119
+ private readonly shutdownSignal: AbortSignal | undefined;
117
120
 
118
121
  constructor(
119
122
  config: PluginConfig,
120
123
  gatewayConfig?: GatewayConfig,
121
124
  modelRegistry?: ModelRegistry,
122
- transcript?: TranscriptManager
125
+ transcript?: TranscriptManager,
126
+ shutdownSignal?: AbortSignal
123
127
  ) {
124
128
  this.config = config;
125
129
  this.summariesDir = path.join(config.memoryDir, "summaries", "hourly");
126
130
  this.modelRegistry = modelRegistry ?? new ModelRegistry(config.memoryDir);
127
131
  this.transcript = transcript;
132
+ this.shutdownSignal = shutdownSignal;
128
133
 
129
134
  // Initialize local LLM client with shared model registry
130
- this.localLlm = new LocalLlmClient(config, this.modelRegistry);
135
+ this.localLlm = new LocalLlmClient(config, this.modelRegistry, shutdownSignal);
131
136
 
132
137
  // Initialize fallback client with gateway config
133
138
  this.fallbackLlm = new FallbackLlmClient(gatewayConfig, fallbackLlmRuntimeContextFromConfig(config));
@@ -190,6 +195,7 @@ export class HourlySummarizer {
190
195
  entries: TranscriptEntry[]
191
196
  ): Promise<HourlySummary | null> {
192
197
  if (entries.length === 0) return null;
198
+ this.shutdownSignal?.throwIfAborted();
193
199
 
194
200
  // Format entries for the LLM
195
201
  const conversation = entries.map((e) => `[${e.role}] ${e.content}`).join("\n\n");
@@ -275,6 +281,8 @@ export class HourlySummarizer {
275
281
  }
276
282
  }
277
283
 
284
+ if (this.shutdownSignal?.aborted) return null;
285
+
278
286
  // Fall back to gateway's default AI
279
287
  log.info("summary generation: falling back to gateway default AI");
280
288
 
@@ -399,6 +407,7 @@ Respond with valid JSON matching this schema:
399
407
  maxTokens: contextSizes.maxOutputTokens,
400
408
  operation: "hourly_summary_extended",
401
409
  priority: "background",
410
+ signal: this.shutdownSignal,
402
411
  }
403
412
  );
404
413
  if (response?.content) {
@@ -422,6 +431,8 @@ Respond with valid JSON matching this schema:
422
431
  }
423
432
  }
424
433
 
434
+ if (this.shutdownSignal?.aborted) return null;
435
+
425
436
  try {
426
437
  const result = await this.fallbackLlm.parseWithSchema(
427
438
  [
@@ -489,6 +500,7 @@ Respond with valid JSON matching this schema:
489
500
  maxTokens: contextSizes.maxOutputTokens,
490
501
  operation: "hourly_summary",
491
502
  priority: "background",
503
+ signal: this.shutdownSignal,
492
504
  }
493
505
  );
494
506
 
@@ -775,6 +787,7 @@ Respond with valid JSON matching this schema:
775
787
 
776
788
  // Main entry point for cron job
777
789
  async runHourly(): Promise<HourlySummarizerRunStats> {
790
+ this.shutdownSignal?.throwIfAborted();
778
791
  log.debug("running hourly summary generation");
779
792
 
780
793
  // Get active sessions from transcript. The scan outcome is kept LOCAL to
@@ -224,6 +224,26 @@ test("auto mode writes active status", async () => {
224
224
  assert.equal(writes[0].options.status, "active");
225
225
  });
226
226
 
227
+ test("a fact write the dedup window suppressed counts as skipped, not created", async () => {
228
+ const { writer } = makeWriter();
229
+ writer.writeSealedMemory = async () => ({ duplicate: true });
230
+ const result = await generateWearableMemories(
231
+ "limitless",
232
+ "2026-06-10",
233
+ [LONG_CONVERSATION],
234
+ settings({ memoryMode: "auto" }),
235
+ REGISTRY,
236
+ {
237
+ extract: extractionReturning([
238
+ { category: "fact", content: "Vendor call happens tomorrow morning.", confidence: 0.8, tags: [] },
239
+ ]),
240
+ writer,
241
+ },
242
+ );
243
+ assert.equal(result.created, 0);
244
+ assert.equal(result.skippedByReason["duplicate-existing"], 1);
245
+ });
246
+
227
247
  test("auto mode still refuses to write a high-impact personal claim active (#2294)", async () => {
228
248
  // `auto` has no trust scoring, and an operator may lower minConfidence to or
229
249
  // below the speculative ceiling the ambient clamp writes — which would let a
@@ -92,6 +92,11 @@ export function memoryStatusForMode(
92
92
  return mode === "auto" || mode === "smart" ? "active" : "pending_review";
93
93
  }
94
94
 
95
+ /** A fact write the fact-dedup window suppressed because a concurrent writer committed the same fact first. */
96
+ export interface WearableDuplicateWrite {
97
+ readonly duplicate: true;
98
+ }
99
+
95
100
  /** Narrow writer interface satisfied by `StorageManager`. */
96
101
  export interface WearableMemoryWriter {
97
102
  /**
@@ -106,7 +111,7 @@ export interface WearableMemoryWriter {
106
111
  status?: MemoryStatus;
107
112
  memoryKind?: "episode" | "note" | "box" | "dream" | "procedural";
108
113
  },
109
- ): Promise<MemoryWriteResult>;
114
+ ): Promise<MemoryWriteResult | WearableDuplicateWrite>;
110
115
  hasFactContentHash(content: string): Promise<boolean>;
111
116
  /**
112
117
  * Locate an earlier wearable write of the same content (any status).
@@ -663,7 +668,9 @@ export async function generateWearableMemories(
663
668
  // #1645: a tombstone-blocked wearable write lands pending_review (no active
664
669
  // copy) — count it as blocked, NOT created, so the sync summary and callers
665
670
  // don't report blocked content as successfully created active memories.
666
- if (writeResult.tombstoneBlocked) {
671
+ if ("duplicate" in writeResult) {
672
+ skip("duplicate-existing");
673
+ } else if (writeResult.tombstoneBlocked) {
667
674
  result.tombstoneBlocked += 1;
668
675
  } else {
669
676
  result.created += 1;
@@ -872,11 +879,11 @@ export async function importNativeMemories(
872
879
  // #1645: a tombstone-blocked native import lands pending_review (no active
873
880
  // copy) — count it as blocked, NOT imported. Still record the provider id in
874
881
  // importedIds so the next sync doesn't re-import and duplicate the pending
875
- // row; the block is permanent for this content.
876
- if (nativeWriteResult.tombstoneBlocked) {
877
- tombstoneBlocked += 1;
878
- } else {
879
- imported += 1;
882
+ // row; the block is permanent for this content. A write the dedup window
883
+ // suppressed (a concurrent writer committed the fact first) counts as neither.
884
+ if (!("duplicate" in nativeWriteResult)) {
885
+ if (nativeWriteResult.tombstoneBlocked) tombstoneBlocked += 1;
886
+ else imported += 1;
880
887
  }
881
888
  importedIds.push(memory.id);
882
889
  }
@@ -13,6 +13,7 @@ import {
13
13
  } from "./corrections.js";
14
14
  import { describeErrorForOperator, WearablesInputError } from "./errors.js";
15
15
  import { inferMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
16
+ import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
16
17
  import type { LocationConfig } from "../location/types.js";
17
18
  import { isSupportPassportPrivateMemory } from "../support-passport/card-projection.js";
18
19
  import {
@@ -194,7 +195,19 @@ export function createWearableMemoryWriter(
194
195
  storage: WearableStorageIo,
195
196
  ): WearableMemoryGenDeps["writer"] {
196
197
  return {
197
- writeSealedMemory: storage.writeSealedMemory.bind(storage),
198
+ writeSealedMemory: async (envelope, extras) => {
199
+ // Facts register in the fact hash index, so extraction and a wearable sync
200
+ // can commit the same new fact. Commit it under the window extraction uses
201
+ // and recheck the index there. Other categories keep the direct write: the
202
+ // index does not cover them, and the corpus scan stays outside the window.
203
+ if (envelope.category !== "fact") return storage.writeSealedMemory(envelope, extras);
204
+ const outcome = await persistUnderFactDedupWindow(
205
+ storage.dir,
206
+ () => storage.hasFactContentHash(extras.contentHashSource ?? envelope.content),
207
+ () => storage.writeSealedMemory(envelope, extras),
208
+ );
209
+ return outcome.suppressed ? { duplicate: true } : outcome.result;
210
+ },
198
211
  findWearableMemoryByContent: async (content: string) =>
199
212
  (await storage.findWearableMemoryByContent(content)) as
200
213
  | { id: string; status: import("../types.js").MemoryStatus | undefined }
@@ -5,7 +5,10 @@ import { tmpdir } from "node:os";
5
5
  import * as path from "node:path";
6
6
  import { test } from "node:test";
7
7
 
8
+ import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
8
9
  import { StorageManager } from "../storage.js";
10
+ import { composeMemoryEnvelope } from "../write-envelope.js";
11
+ import { createWearableMemoryWriter } from "./service.js";
9
12
 
10
13
  function makeStorage(): { storage: StorageManager; dir: string } {
11
14
  const dir = mkdtempSync(path.join(tmpdir(), "remnic-wearable-io-"));
@@ -330,3 +333,25 @@ test("a symlinked _fusion dir that resolves INSIDE the memory dir is refused, no
330
333
  rmSync(dir, { recursive: true, force: true });
331
334
  }
332
335
  });
336
+
337
+ test("a wearable fact write loses to a fact another writer committed first and writes nothing", async () => {
338
+ const { storage, dir } = makeStorage();
339
+ try {
340
+ await storage.ensureDirectories();
341
+ const fact = "Dana moved the review meeting to Thursday afternoon.";
342
+ const writer = createWearableMemoryWriter(storage);
343
+ const envelope = composeMemoryEnvelope(
344
+ { content: fact, category: "fact", tags: ["wearable"] },
345
+ { source: "wearable:limitless" },
346
+ );
347
+ await persistUnderFactDedupWindow(storage.dir, async () => false, () => storage.writeMemory("fact", fact));
348
+
349
+ const written = await writer.writeSealedMemory(envelope, { contentHashSource: fact, status: "active" });
350
+
351
+ assert.deepEqual(written, { duplicate: true });
352
+ const copies = (await storage.readAllMemories()).filter((memory) => memory.content.includes(fact));
353
+ assert.equal(copies.length, 1, "the corpus holds one copy of the fact");
354
+ } finally {
355
+ rmSync(dir, { recursive: true, force: true });
356
+ }
357
+ });
@@ -1,61 +0,0 @@
1
- import {
2
- runLocationSync
3
- } from "./chunk-SUSW74WU.js";
4
- import {
5
- log
6
- } from "./chunk-3WCASNQZ.js";
7
-
8
- // src/location/scheduler.ts
9
- var LOCATION_SYNC_DEFAULT_INTERVAL_MS = 24 * 60 * 60 * 1e3;
10
- function startLocationAutoSync(settings, deps = {}) {
11
- const logger = deps.log ?? log;
12
- const intervalMs = settings.intervalMs ?? LOCATION_SYNC_DEFAULT_INTERVAL_MS;
13
- const setIntervalImpl = settings.setInterval ?? ((fn, ms) => setInterval(fn, ms));
14
- const clearIntervalImpl = settings.clearInterval ?? ((handle) => clearInterval(handle));
15
- const sync = deps.sync ?? ((deps2) => runLocationSync({ config: deps2.config, memoryDir: deps2.memoryDir }));
16
- let inFlight = null;
17
- let stopped = false;
18
- const timer = setIntervalImpl(() => {
19
- if (inFlight !== null || stopped) return;
20
- inFlight = (async () => {
21
- try {
22
- const runs = await sync({ config: settings.config, memoryDir: settings.memoryDir });
23
- const failed = runs.some((run) => run.results.some((result) => result.status === "failed"));
24
- if (failed) {
25
- logger.warn("location auto-sync: one or more sources failed \u2014 retrying on the next tick");
26
- } else {
27
- logger.info(
28
- `location auto-sync: refreshed ${settings.config.syncDays}-day window across ${settings.config.sources.length} source(s)`
29
- );
30
- }
31
- } catch (err) {
32
- if (err instanceof Error && err.name === "AbortError") return;
33
- logger.warn(
34
- `location auto-sync failed: ${err instanceof Error ? err.name : "unknown error"} \u2014 retrying on the next tick`
35
- );
36
- } finally {
37
- inFlight = null;
38
- }
39
- })();
40
- }, intervalMs);
41
- timer.unref?.();
42
- return {
43
- async tick() {
44
- const runs = await sync({ config: settings.config, memoryDir: settings.memoryDir });
45
- logger.info(
46
- `location auto-sync tick: ${runs.length} day(s) across ${settings.config.sources.length} source(s)`
47
- );
48
- },
49
- async stop() {
50
- stopped = true;
51
- clearIntervalImpl(timer);
52
- await inFlight;
53
- }
54
- };
55
- }
56
-
57
- export {
58
- LOCATION_SYNC_DEFAULT_INTERVAL_MS,
59
- startLocationAutoSync
60
- };
61
- //# sourceMappingURL=chunk-2FGLEF7C.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/location/scheduler.ts"],"sourcesContent":["/**\n * Location auto-sync — in-process periodic day sync (issue #2047).\n *\n * Arms one repeating timer that runs the SAME shared surface runner\n * (`runLocationSync`) as the CLI/MCP/HTTP surfaces over the configured\n * `location.syncDays` window ending yesterday, so late provider uploads are\n * picked up on the next tick. Master default-off: the caller (maintenance\n * scheduler registration) arms this only when `location.enabled` is true and\n * at least one source is enabled; a forced manual sync never bypasses those\n * gates — they live inside the pipeline itself.\n *\n * The timer is unref'd so one-shot CLI processes exit naturally; ticks are\n * serialized (an overlapping tick is skipped, not queued). ponytail: fixed\n * default cadence (daily) with no per-install override — add a\n * location.syncIntervalHours config field if installs need it.\n */\n\nimport { log } from \"../logger.js\";\nimport { runLocationSync, type LocationSyncRuns } from \"./surfaces.js\";\nimport type { LocationConfig } from \"./types.js\";\n\nexport const LOCATION_SYNC_DEFAULT_INTERVAL_MS = 24 * 60 * 60 * 1000;\n\nexport interface LocationAutoSyncSettings {\n config: LocationConfig;\n memoryDir: string;\n /** Tick cadence in ms; defaults to LOCATION_SYNC_DEFAULT_INTERVAL_MS. */\n intervalMs?: number;\n /** Clock/timer seam for tests. */\n setInterval?: (fn: () => void, ms: number) => { unref?: () => void };\n clearInterval?: (handle: unknown) => void;\n}\n\nexport interface LocationAutoSyncDeps {\n /** Run a sync (defaults to the shared surface runner). */\n sync?: (deps: { config: LocationConfig; memoryDir: string }) => Promise<LocationSyncRuns>;\n log?: { info(message: string): void; warn(message: string): void };\n}\n\nexport interface LocationAutoSyncHandle {\n /** Run one tick now (first-run hook and test seam). */\n tick(): Promise<void>;\n stop(): Promise<void>;\n}\n\nexport function startLocationAutoSync(\n settings: LocationAutoSyncSettings,\n deps: LocationAutoSyncDeps = {},\n): LocationAutoSyncHandle {\n const logger = deps.log ?? log;\n const intervalMs = settings.intervalMs ?? LOCATION_SYNC_DEFAULT_INTERVAL_MS;\n const setIntervalImpl = settings.setInterval ?? ((fn: () => void, ms: number) => setInterval(fn, ms));\n const clearIntervalImpl = settings.clearInterval ?? ((handle: unknown) => clearInterval(handle as NodeJS.Timeout));\n const sync =\n deps.sync ??\n ((deps: { config: LocationConfig; memoryDir: string }) =>\n runLocationSync({ config: deps.config, memoryDir: deps.memoryDir }));\n let inFlight: Promise<void> | null = null;\n let stopped = false;\n const timer = setIntervalImpl(() => {\n if (inFlight !== null || stopped) return;\n inFlight = (async () => {\n try {\n const runs = await sync({ config: settings.config, memoryDir: settings.memoryDir });\n const failed = runs.some((run) => run.results.some((result) => result.status === \"failed\"));\n if (failed) {\n logger.warn(\"location auto-sync: one or more sources failed — retrying on the next tick\");\n } else {\n logger.info(\n `location auto-sync: refreshed ${settings.config.syncDays}-day window across ${settings.config.sources.length} source(s)`,\n );\n }\n } catch (err) {\n // An abort raised by stop() is intentional shutdown, not a failure.\n if (err instanceof Error && err.name === \"AbortError\") return;\n logger.warn(\n `location auto-sync failed: ${err instanceof Error ? err.name : \"unknown error\"} — retrying on the next tick`,\n );\n } finally {\n inFlight = null;\n }\n })();\n }, intervalMs);\n timer.unref?.();\n return {\n async tick() {\n const runs = await sync({ config: settings.config, memoryDir: settings.memoryDir });\n logger.info(\n `location auto-sync tick: ${runs.length} day(s) across ${settings.config.sources.length} source(s)`,\n );\n },\n async stop() {\n stopped = true;\n clearIntervalImpl(timer);\n await inFlight;\n },\n };\n}\n"],"mappings":";;;;;;;;AAqBO,IAAM,oCAAoC,KAAK,KAAK,KAAK;AAwBzD,SAAS,sBACd,UACA,OAA6B,CAAC,GACN;AACxB,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,aAAa,SAAS,cAAc;AAC1C,QAAM,kBAAkB,SAAS,gBAAgB,CAAC,IAAgB,OAAe,YAAY,IAAI,EAAE;AACnG,QAAM,oBAAoB,SAAS,kBAAkB,CAAC,WAAoB,cAAc,MAAwB;AAChH,QAAM,OACJ,KAAK,SACJ,CAACA,UACA,gBAAgB,EAAE,QAAQA,MAAK,QAAQ,WAAWA,MAAK,UAAU,CAAC;AACtE,MAAI,WAAiC;AACrC,MAAI,UAAU;AACd,QAAM,QAAQ,gBAAgB,MAAM;AAClC,QAAI,aAAa,QAAQ,QAAS;AAClC,gBAAY,YAAY;AACtB,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,EAAE,QAAQ,SAAS,QAAQ,WAAW,SAAS,UAAU,CAAC;AAClF,cAAM,SAAS,KAAK,KAAK,CAAC,QAAQ,IAAI,QAAQ,KAAK,CAAC,WAAW,OAAO,WAAW,QAAQ,CAAC;AAC1F,YAAI,QAAQ;AACV,iBAAO,KAAK,iFAA4E;AAAA,QAC1F,OAAO;AACL,iBAAO;AAAA,YACL,iCAAiC,SAAS,OAAO,QAAQ,sBAAsB,SAAS,OAAO,QAAQ,MAAM;AAAA,UAC/G;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AAEZ,YAAI,eAAe,SAAS,IAAI,SAAS,aAAc;AACvD,eAAO;AAAA,UACL,8BAA8B,eAAe,QAAQ,IAAI,OAAO,eAAe;AAAA,QACjF;AAAA,MACF,UAAE;AACA,mBAAW;AAAA,MACb;AAAA,IACF,GAAG;AAAA,EACL,GAAG,UAAU;AACb,QAAM,QAAQ;AACd,SAAO;AAAA,IACL,MAAM,OAAO;AACX,YAAM,OAAO,MAAM,KAAK,EAAE,QAAQ,SAAS,QAAQ,WAAW,SAAS,UAAU,CAAC;AAClF,aAAO;AAAA,QACL,4BAA4B,KAAK,MAAM,kBAAkB,SAAS,OAAO,QAAQ,MAAM;AAAA,MACzF;AAAA,IACF;AAAA,IACA,MAAM,OAAO;AACX,gBAAU;AACV,wBAAkB,KAAK;AACvB,YAAM;AAAA,IACR;AAAA,EACF;AACF;","names":["deps"]}