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