@remnic/core 9.69.87 → 9.69.89
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 +56 -49
- package/dist/access-authorization-probe.js +57 -50
- package/dist/access-boundary.js +56 -49
- package/dist/access-citation-namespace.js +2 -2
- package/dist/access-cli.js +129 -122
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +56 -49
- package/dist/access-http-briefing.js +57 -50
- package/dist/access-http-offline-stream.d.ts.map +1 -1
- package/dist/access-http-offline-stream.js +1 -1
- package/dist/access-http-query.js +58 -51
- package/dist/access-http.js +78 -71
- package/dist/access-identity-continuity-surface.js +56 -49
- package/dist/access-lcm-surface.js +56 -49
- package/dist/access-mcp-output-schemas.d.ts.map +1 -1
- package/dist/access-mcp-output-schemas.js +1 -1
- package/dist/access-mcp.js +69 -62
- package/dist/access-namespace-preflight.js +56 -49
- package/dist/access-observe-write-category.js +20 -13
- package/dist/access-observe-write-surface.js +56 -49
- package/dist/access-offline-manifest.js +16 -10
- package/dist/access-operations-batch.js +60 -53
- package/dist/access-operations.js +67 -60
- package/dist/access-recall-concurrency.js +56 -49
- package/dist/access-recall-response.js +56 -49
- package/dist/access-recall-surface.js +56 -49
- package/dist/access-schema.js +16 -7
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +56 -49
- package/dist/access-surface-catalog.js +58 -51
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +24 -18
- package/dist/active-recall.js.map +1 -1
- package/dist/{auto-sync-6IPX2BAC.js → auto-sync-U5N3CB5Y.js} +7 -7
- package/dist/briefing.js +11 -11
- package/dist/buffer.js +7 -7
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
- package/dist/{capsule-merge-3MNURAQ3.js → capsule-merge-ZCXUPLNJ.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +14 -14
- 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-JE334SIA.js → chunk-22DYJLFN.js} +2 -2
- package/dist/{chunk-YTQGGO3K.js → chunk-22LMCQ5B.js} +85 -82
- package/dist/{chunk-YTQGGO3K.js.map → chunk-22LMCQ5B.js.map} +1 -1
- package/dist/{chunk-JGSQ6P4D.js → chunk-2FX374UI.js} +2 -2
- package/dist/{chunk-SY5KDWGU.js → chunk-2WLMWP2T.js} +2 -2
- package/dist/{chunk-6TT6HRE4.js → chunk-3AGHSQPM.js} +2 -2
- package/dist/{chunk-P6WFK4ZJ.js → chunk-3O5BB45T.js} +4 -4
- package/dist/{chunk-3C3J243X.js → chunk-3QKO4QN7.js} +3 -3
- package/dist/{chunk-KFCMMDHH.js → chunk-3YD44CH2.js} +2 -2
- package/dist/{chunk-FRCESVV6.js → chunk-4HUCP4R4.js} +18 -18
- package/dist/chunk-4HUCP4R4.js.map +1 -0
- package/dist/chunk-4NSLQ7EU.js +137 -0
- package/dist/chunk-4NSLQ7EU.js.map +1 -0
- package/dist/{chunk-7DE2NOON.js → chunk-4O4XCBOS.js} +3 -3
- package/dist/{chunk-GDYU6VF4.js → chunk-5AYUHSAU.js} +2 -2
- package/dist/{chunk-ICZQG4XE.js → chunk-5FAGCOZD.js} +4 -4
- package/dist/{chunk-VKQI47DJ.js → chunk-5FNZ3G6X.js} +4 -4
- package/dist/{chunk-CK5KOVHW.js → chunk-5R627MYY.js} +3 -2
- package/dist/{chunk-CK5KOVHW.js.map → chunk-5R627MYY.js.map} +1 -1
- package/dist/{chunk-VQNONC4E.js → chunk-5STPRPE7.js} +2 -2
- package/dist/{chunk-52II7PB6.js → chunk-645WOU5I.js} +4 -4
- package/dist/{chunk-SRZW7FNU.js → chunk-64ZUALXK.js} +2 -2
- package/dist/{chunk-4BZP2B4U.js → chunk-6MNKDTDI.js} +2 -2
- package/dist/{chunk-DUJYRVUD.js → chunk-6O2VUWV6.js} +3 -3
- package/dist/chunk-75PT4N7Q.js +75 -0
- package/dist/chunk-75PT4N7Q.js.map +1 -0
- package/dist/{chunk-HESDNYRB.js → chunk-76M5OK7V.js} +2 -2
- package/dist/{chunk-FAN73COB.js → chunk-7D7OUO5W.js} +2 -2
- package/dist/{chunk-DZ525SNQ.js → chunk-7QE46QXI.js} +1 -1
- package/dist/{chunk-VX6GCKBS.js → chunk-7WBSMXGC.js} +4 -4
- package/dist/{chunk-VVDWZRZG.js → chunk-A6RVIHHK.js} +3 -3
- package/dist/{chunk-M536WNTQ.js → chunk-AEJ3TE5H.js} +2 -2
- package/dist/{chunk-72JNNJJJ.js → chunk-AXC4WSF3.js} +3 -3
- package/dist/chunk-AXGJMDBS.js +684 -0
- package/dist/chunk-AXGJMDBS.js.map +1 -0
- package/dist/{chunk-UA5DX5C3.js → chunk-AZIOEOK4.js} +2 -2
- package/dist/{chunk-N4S4NUX5.js → chunk-B3O3IRZN.js} +1 -1
- package/dist/{chunk-XTYX6V52.js → chunk-BJVO4H5C.js} +2 -2
- package/dist/{chunk-7LCGFUMB.js → chunk-BUKK5SWA.js} +4 -4
- package/dist/{chunk-TM3RXL4G.js → chunk-BUQKSQXL.js} +2 -2
- package/dist/chunk-C6HPNPNK.js +64 -0
- package/dist/chunk-C6HPNPNK.js.map +1 -0
- package/dist/{chunk-RB776UH3.js → chunk-C7V3KPKJ.js} +235 -93
- package/dist/chunk-C7V3KPKJ.js.map +1 -0
- package/dist/{chunk-FOH7AMMI.js → chunk-CALBQ2NG.js} +71 -72
- package/dist/chunk-CALBQ2NG.js.map +1 -0
- package/dist/{chunk-37S3IMYB.js → chunk-DGS34QSY.js} +2 -2
- package/dist/{chunk-4IEOGIPB.js → chunk-DO4C4AXD.js} +2 -2
- package/dist/{chunk-SPSRGEDQ.js → chunk-EAQQ2FD3.js} +9 -9
- package/dist/{chunk-3BTFBQLO.js → chunk-EFHNFTWB.js} +49 -3
- package/dist/chunk-EFHNFTWB.js.map +1 -0
- package/dist/{chunk-C3QXITIP.js → chunk-EQUD7FP4.js} +2 -2
- package/dist/chunk-ETUZFFPU.js +218 -0
- package/dist/chunk-ETUZFFPU.js.map +1 -0
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-YITN5WXI.js → chunk-FC52YWAO.js} +2 -2
- package/dist/{chunk-ZI7QA4MK.js → chunk-FEIXG5FA.js} +2 -2
- package/dist/{chunk-CLXTDOKV.js → chunk-FUNBJQYR.js} +2 -2
- package/dist/{chunk-CLSYE5OZ.js → chunk-G2STTRYB.js} +2 -2
- package/dist/{chunk-TDYAIXHK.js → chunk-H36X4NRH.js} +5 -5
- package/dist/{chunk-3GQC3NMB.js → chunk-H56VY4BY.js} +5 -5
- package/dist/{chunk-PUKTBULQ.js → chunk-HWLIMQE2.js} +2 -2
- package/dist/{chunk-V2ES4WCZ.js → chunk-IIARQTZ2.js} +2 -2
- package/dist/{chunk-QEV26L2F.js → chunk-IKDRGVVJ.js} +2 -2
- package/dist/{chunk-WQZZDN5M.js → chunk-IVEHY2VT.js} +254 -377
- package/dist/chunk-IVEHY2VT.js.map +1 -0
- package/dist/{chunk-LS3XH3UE.js → chunk-IYIBGSHJ.js} +2 -2
- package/dist/{chunk-YSJTCOHR.js → chunk-J27JML5O.js} +2 -2
- package/dist/{chunk-AQK54Q7O.js → chunk-JB56XYHP.js} +2 -2
- package/dist/{chunk-NE6PHPLG.js → chunk-JBH26P62.js} +10 -10
- package/dist/{chunk-CWHV574S.js → chunk-JUT3OR2L.js} +6 -6
- package/dist/{chunk-6ET2VII7.js → chunk-JYG2334B.js} +2 -2
- package/dist/{chunk-LF5IGOOD.js → chunk-JYM4Y6SO.js} +16 -16
- package/dist/{chunk-FB64XE3R.js → chunk-JZ4TJHR2.js} +2 -2
- package/dist/{chunk-EHS5MJEF.js → chunk-KTKHO3UI.js} +3 -3
- package/dist/{chunk-WIKO4SCR.js → chunk-LHRQBMJN.js} +2 -2
- package/dist/{chunk-K4J4E7WV.js → chunk-LO4UKPKX.js} +2 -2
- package/dist/{chunk-JN72EPOE.js → chunk-MAAL6OZV.js} +7 -7
- package/dist/chunk-MAAL6OZV.js.map +1 -0
- package/dist/{chunk-MY2PLXSM.js → chunk-MCTM4YPL.js} +2 -1
- package/dist/{chunk-MY2PLXSM.js.map → chunk-MCTM4YPL.js.map} +1 -1
- package/dist/{chunk-6NQ4LSMS.js → chunk-MGZMC6WN.js} +47 -47
- package/dist/chunk-MSAI3Q7D.js +372 -0
- package/dist/chunk-MSAI3Q7D.js.map +1 -0
- package/dist/{chunk-BMYZTZ5D.js → chunk-MY7B3HYC.js} +2 -2
- package/dist/{chunk-NB3LRY4T.js → chunk-N4PYB235.js} +2 -2
- package/dist/{chunk-S3YM4SZQ.js → chunk-N7QV3CMX.js} +2 -2
- package/dist/{chunk-L6UZEAID.js → chunk-NJVR724O.js} +4 -4
- package/dist/{chunk-VLLD4TOB.js → chunk-NMZVDRVP.js} +2 -2
- package/dist/{chunk-P2WZHLUF.js → chunk-NOL7SEWN.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
- package/dist/{chunk-6L3PBSAR.js → chunk-PMNLL2TL.js} +2 -2
- package/dist/{chunk-VYCOJKSX.js → chunk-PO7NXNGC.js} +2 -2
- package/dist/{chunk-4IIMMZZK.js → chunk-PULDSMCJ.js} +10 -10
- package/dist/{chunk-G6SVZWPB.js → chunk-PZX3AQLF.js} +44 -1
- package/dist/chunk-PZX3AQLF.js.map +1 -0
- package/dist/{chunk-HBBHL4YF.js → chunk-QOXNTKXB.js} +2 -2
- package/dist/{chunk-CUCWX3MO.js → chunk-QZLCVO2V.js} +2 -2
- package/dist/{chunk-NE7I2RCT.js → chunk-R3X2T2LP.js} +2 -2
- package/dist/{chunk-S7GCT7NU.js → chunk-RBITPCMK.js} +8 -8
- package/dist/{chunk-EVBI7S4J.js → chunk-RMDGAAZF.js} +4 -4
- package/dist/{chunk-BJRAELDH.js → chunk-SCXURCVT.js} +3 -3
- package/dist/{chunk-EQ6YOIKO.js → chunk-ST2RQUXC.js} +3 -3
- package/dist/{chunk-CS52MFDV.js → chunk-SZG4WFAG.js} +2 -2
- package/dist/{chunk-BFO3IR56.js → chunk-TPEZ2SHV.js} +2 -2
- package/dist/chunk-TYT4TEQV.js +45 -0
- package/dist/chunk-TYT4TEQV.js.map +1 -0
- package/dist/{chunk-YRLHEEQT.js → chunk-U6VC6RP3.js} +2 -2
- package/dist/chunk-UCPGOUDZ.js +726 -0
- package/dist/chunk-UCPGOUDZ.js.map +1 -0
- package/dist/{chunk-MOQPEFLN.js → chunk-UP75KL3L.js} +2 -2
- package/dist/{chunk-W4AOR52H.js → chunk-UQDC3W77.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
- package/dist/{chunk-D5F7TKMH.js → chunk-V4JZCTMK.js} +2 -2
- package/dist/{chunk-MQJL6CDZ.js → chunk-VA4O3U7U.js} +4 -4
- package/dist/{chunk-4CDYONWO.js → chunk-VDO32NLK.js} +3 -3
- package/dist/{chunk-SOZUJCME.js → chunk-VHDKPGMG.js} +6 -6
- package/dist/{chunk-QWW2JDDX.js → chunk-WNRTXEWW.js} +6 -6
- package/dist/{chunk-NR2ASHBL.js → chunk-XOV7NRY5.js} +2 -2
- package/dist/{chunk-KP7SANCF.js → chunk-YPI4RQWV.js} +2 -2
- package/dist/{chunk-JKMHZF3Q.js → chunk-YSWJXOOL.js} +2 -2
- package/dist/{chunk-LQG5O5EP.js → chunk-Z3O53WZR.js} +2 -2
- package/dist/{chunk-QVS22WTO.js → chunk-ZG5DWWKK.js} +2 -2
- package/dist/cli.js +99 -92
- package/dist/coding/export-okf-codegraph.js +12 -12
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +12 -12
- package/dist/config.js +24 -18
- package/dist/connectors/codex-materialize-runner.js +11 -11
- package/dist/connectors/index.js +11 -11
- package/dist/console/state.d.ts +30 -0
- package/dist/console/state.d.ts.map +1 -1
- package/dist/contradiction/index.js +5 -5
- package/dist/convergence-refresh.js +13 -13
- package/dist/corpus-watermark.js +13 -13
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-http-glue.js +16 -8
- package/dist/embedding-fallback.d.ts +41 -31
- package/dist/embedding-fallback.d.ts.map +1 -1
- package/dist/embedding-fallback.js +9 -1
- package/dist/embedding-generation-lock.d.ts +49 -0
- package/dist/embedding-generation-lock.d.ts.map +1 -0
- package/dist/embedding-generation-lock.js +19 -0
- package/dist/embedding-index-storage.d.ts +276 -0
- package/dist/embedding-index-storage.d.ts.map +1 -0
- package/dist/embedding-index-storage.js +28 -0
- package/dist/entity-retrieval.js +12 -12
- package/dist/event-order-recall.js +3 -3
- package/dist/explicit-capture.js +6 -6
- package/dist/external-wiki-access.js +57 -50
- package/dist/extraction.js +17 -17
- package/dist/{first-start-migration-7OATHJKB.js → first-start-migration-GAT7G733.js} +4 -4
- package/dist/{graph-edge-decay-KQGS3L3B.js → graph-edge-decay-MKIRZ762.js} +5 -5
- 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 +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +195 -164
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +14 -14
- package/dist/maintenance/memory-governance.js +11 -11
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +11 -11
- package/dist/maintenance/rebuild-memory-projection.js +12 -12
- package/dist/memory-browse-operations.js +57 -50
- package/dist/memory-subject.js +3 -3
- package/dist/namespaces/migrate.js +12 -12
- package/dist/namespaces/storage.js +11 -11
- package/dist/offline-sync-changeset-generations.d.ts +82 -0
- package/dist/offline-sync-changeset-generations.d.ts.map +1 -0
- package/dist/offline-sync-changeset-generations.js +21 -0
- package/dist/offline-sync-embedding-generation.d.ts +257 -0
- package/dist/offline-sync-embedding-generation.d.ts.map +1 -0
- package/dist/offline-sync-embedding-generation.js +44 -0
- package/dist/offline-sync-embedding-generation.js.map +1 -0
- package/dist/offline-sync-exclude-globs.d.ts +15 -0
- package/dist/offline-sync-exclude-globs.d.ts.map +1 -1
- package/dist/offline-sync-exclude-globs.js +11 -3
- package/dist/offline-sync-file-io.d.ts +7 -0
- package/dist/offline-sync-file-io.d.ts.map +1 -1
- package/dist/offline-sync-file-io.js +14 -2
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/offline-sync-runtime-state.d.ts +5 -0
- package/dist/offline-sync-runtime-state.d.ts.map +1 -0
- package/dist/offline-sync-runtime-state.js +14 -0
- package/dist/offline-sync-runtime-state.js.map +1 -0
- package/dist/offline-sync-snapshot-stream.d.ts +26 -0
- package/dist/offline-sync-snapshot-stream.d.ts.map +1 -0
- package/dist/offline-sync-snapshot-stream.js +29 -0
- package/dist/offline-sync-snapshot-stream.js.map +1 -0
- package/dist/offline-sync-upload-staging.d.ts +34 -0
- package/dist/offline-sync-upload-staging.d.ts.map +1 -0
- package/dist/offline-sync-upload-staging.js +26 -0
- package/dist/offline-sync-upload-staging.js.map +1 -0
- package/dist/offline-sync.d.ts +58 -28
- package/dist/offline-sync.d.ts.map +1 -1
- package/dist/offline-sync.js +22 -8
- package/dist/operator-doctor-corpus.js +14 -14
- package/dist/operator-toolkit.js +35 -29
- package/dist/orchestration/maintenance.js +22 -22
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +129 -122
- package/dist/recall-navigation-operations.js +57 -50
- package/dist/recall-navigation.js +56 -49
- package/dist/reconcile/manifest.js +4 -4
- package/dist/{resolution-DNX7FO7A.js → resolution-K4A3VPKZ.js} +6 -6
- package/dist/resolution-K4A3VPKZ.js.map +1 -0
- package/dist/resume-bundles.js +25 -19
- package/dist/salvage-envelope.js +5 -5
- package/dist/secure-store/index.js +2 -2
- package/dist/secure-store/secure-fs.d.ts +14 -1
- package/dist/secure-store/secure-fs.d.ts.map +1 -1
- package/dist/semantic-consolidation.js +12 -12
- package/dist/semantic-rule-promotion.js +11 -11
- package/dist/semantic-rule-verifier.js +11 -11
- package/dist/source-agent-qualifier.js +14 -14
- package/dist/standup/build.js +11 -11
- package/dist/{state-ITXJKXRE.js → state-JB47KHYV.js} +2 -2
- package/dist/state-JB47KHYV.js.map +1 -0
- package/dist/storage.d.ts +9 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +10 -10
- package/dist/subject-config.js +4 -4
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +59 -52
- package/dist/support-passport/offline-sync-guard.d.ts +6 -2
- package/dist/support-passport/offline-sync-guard.d.ts.map +1 -1
- package/dist/{trace-ZFVRH4GO.js → trace-LXXCMZJA.js} +3 -3
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/export-okf.js +12 -12
- package/dist/{tui-55UJMJX7.js → tui-2Z5WOV7L.js} +3 -3
- package/dist/tui-2Z5WOV7L.js.map +1 -0
- package/dist/verified-recall.js +11 -11
- package/dist/who-knows-cli.js +12 -12
- package/dist/who-knows.js +11 -11
- package/dist/write-envelope.js +4 -4
- package/package.json +2 -2
- package/src/access-http-offline-stream.ts +3 -0
- package/src/access-mcp-output-schemas.ts +1 -0
- package/src/access-service.ts +7 -18
- package/src/console/state.test.ts +56 -0
- package/src/console/state.ts +83 -0
- package/src/embedding-fallback.ts +325 -141
- package/src/embedding-generation-lock.ts +141 -0
- package/src/embedding-index-storage.test.ts +245 -0
- package/src/embedding-index-storage.ts +1068 -0
- package/src/index.ts +3 -3
- package/src/offline-sync-changeset-generations.test.ts +962 -0
- package/src/offline-sync-changeset-generations.ts +549 -0
- package/src/offline-sync-embedding-generation.test.ts +2173 -0
- package/src/offline-sync-embedding-generation.ts +1153 -0
- package/src/offline-sync-exclude-globs.ts +112 -0
- package/src/offline-sync-file-io.ts +48 -0
- package/src/offline-sync-runtime-state.ts +48 -0
- package/src/offline-sync-snapshot-stream.ts +99 -0
- package/src/offline-sync-upload-staging.ts +291 -0
- package/src/offline-sync.test.ts +164 -1
- package/src/offline-sync.ts +372 -454
- package/src/orchestrator.ts +2 -1
- package/src/secure-store/secure-fs.ts +26 -6
- package/src/storage.ts +11 -6
- package/src/support-passport/offline-sync-guard.ts +16 -2
- package/dist/chunk-3BTFBQLO.js.map +0 -1
- package/dist/chunk-AREGMX2J.js +0 -50
- package/dist/chunk-AREGMX2J.js.map +0 -1
- package/dist/chunk-FOH7AMMI.js.map +0 -1
- package/dist/chunk-FRCESVV6.js.map +0 -1
- package/dist/chunk-G6SVZWPB.js.map +0 -1
- package/dist/chunk-JN72EPOE.js.map +0 -1
- package/dist/chunk-RB776UH3.js.map +0 -1
- package/dist/chunk-WQZZDN5M.js.map +0 -1
- /package/dist/{auto-sync-6IPX2BAC.js.map → auto-sync-U5N3CB5Y.js.map} +0 -0
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
- /package/dist/{capsule-merge-3MNURAQ3.js.map → capsule-merge-ZCXUPLNJ.js.map} +0 -0
- /package/dist/{chunk-JE334SIA.js.map → chunk-22DYJLFN.js.map} +0 -0
- /package/dist/{chunk-JGSQ6P4D.js.map → chunk-2FX374UI.js.map} +0 -0
- /package/dist/{chunk-SY5KDWGU.js.map → chunk-2WLMWP2T.js.map} +0 -0
- /package/dist/{chunk-6TT6HRE4.js.map → chunk-3AGHSQPM.js.map} +0 -0
- /package/dist/{chunk-P6WFK4ZJ.js.map → chunk-3O5BB45T.js.map} +0 -0
- /package/dist/{chunk-3C3J243X.js.map → chunk-3QKO4QN7.js.map} +0 -0
- /package/dist/{chunk-KFCMMDHH.js.map → chunk-3YD44CH2.js.map} +0 -0
- /package/dist/{chunk-7DE2NOON.js.map → chunk-4O4XCBOS.js.map} +0 -0
- /package/dist/{chunk-GDYU6VF4.js.map → chunk-5AYUHSAU.js.map} +0 -0
- /package/dist/{chunk-ICZQG4XE.js.map → chunk-5FAGCOZD.js.map} +0 -0
- /package/dist/{chunk-VKQI47DJ.js.map → chunk-5FNZ3G6X.js.map} +0 -0
- /package/dist/{chunk-VQNONC4E.js.map → chunk-5STPRPE7.js.map} +0 -0
- /package/dist/{chunk-52II7PB6.js.map → chunk-645WOU5I.js.map} +0 -0
- /package/dist/{chunk-SRZW7FNU.js.map → chunk-64ZUALXK.js.map} +0 -0
- /package/dist/{chunk-4BZP2B4U.js.map → chunk-6MNKDTDI.js.map} +0 -0
- /package/dist/{chunk-DUJYRVUD.js.map → chunk-6O2VUWV6.js.map} +0 -0
- /package/dist/{chunk-HESDNYRB.js.map → chunk-76M5OK7V.js.map} +0 -0
- /package/dist/{chunk-FAN73COB.js.map → chunk-7D7OUO5W.js.map} +0 -0
- /package/dist/{chunk-DZ525SNQ.js.map → chunk-7QE46QXI.js.map} +0 -0
- /package/dist/{chunk-VX6GCKBS.js.map → chunk-7WBSMXGC.js.map} +0 -0
- /package/dist/{chunk-VVDWZRZG.js.map → chunk-A6RVIHHK.js.map} +0 -0
- /package/dist/{chunk-M536WNTQ.js.map → chunk-AEJ3TE5H.js.map} +0 -0
- /package/dist/{chunk-72JNNJJJ.js.map → chunk-AXC4WSF3.js.map} +0 -0
- /package/dist/{chunk-UA5DX5C3.js.map → chunk-AZIOEOK4.js.map} +0 -0
- /package/dist/{chunk-N4S4NUX5.js.map → chunk-B3O3IRZN.js.map} +0 -0
- /package/dist/{chunk-XTYX6V52.js.map → chunk-BJVO4H5C.js.map} +0 -0
- /package/dist/{chunk-7LCGFUMB.js.map → chunk-BUKK5SWA.js.map} +0 -0
- /package/dist/{chunk-TM3RXL4G.js.map → chunk-BUQKSQXL.js.map} +0 -0
- /package/dist/{chunk-37S3IMYB.js.map → chunk-DGS34QSY.js.map} +0 -0
- /package/dist/{chunk-4IEOGIPB.js.map → chunk-DO4C4AXD.js.map} +0 -0
- /package/dist/{chunk-SPSRGEDQ.js.map → chunk-EAQQ2FD3.js.map} +0 -0
- /package/dist/{chunk-C3QXITIP.js.map → chunk-EQUD7FP4.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-YITN5WXI.js.map → chunk-FC52YWAO.js.map} +0 -0
- /package/dist/{chunk-ZI7QA4MK.js.map → chunk-FEIXG5FA.js.map} +0 -0
- /package/dist/{chunk-CLXTDOKV.js.map → chunk-FUNBJQYR.js.map} +0 -0
- /package/dist/{chunk-CLSYE5OZ.js.map → chunk-G2STTRYB.js.map} +0 -0
- /package/dist/{chunk-TDYAIXHK.js.map → chunk-H36X4NRH.js.map} +0 -0
- /package/dist/{chunk-3GQC3NMB.js.map → chunk-H56VY4BY.js.map} +0 -0
- /package/dist/{chunk-PUKTBULQ.js.map → chunk-HWLIMQE2.js.map} +0 -0
- /package/dist/{chunk-V2ES4WCZ.js.map → chunk-IIARQTZ2.js.map} +0 -0
- /package/dist/{chunk-QEV26L2F.js.map → chunk-IKDRGVVJ.js.map} +0 -0
- /package/dist/{chunk-LS3XH3UE.js.map → chunk-IYIBGSHJ.js.map} +0 -0
- /package/dist/{chunk-YSJTCOHR.js.map → chunk-J27JML5O.js.map} +0 -0
- /package/dist/{chunk-AQK54Q7O.js.map → chunk-JB56XYHP.js.map} +0 -0
- /package/dist/{chunk-NE6PHPLG.js.map → chunk-JBH26P62.js.map} +0 -0
- /package/dist/{chunk-CWHV574S.js.map → chunk-JUT3OR2L.js.map} +0 -0
- /package/dist/{chunk-6ET2VII7.js.map → chunk-JYG2334B.js.map} +0 -0
- /package/dist/{chunk-LF5IGOOD.js.map → chunk-JYM4Y6SO.js.map} +0 -0
- /package/dist/{chunk-FB64XE3R.js.map → chunk-JZ4TJHR2.js.map} +0 -0
- /package/dist/{chunk-EHS5MJEF.js.map → chunk-KTKHO3UI.js.map} +0 -0
- /package/dist/{chunk-WIKO4SCR.js.map → chunk-LHRQBMJN.js.map} +0 -0
- /package/dist/{chunk-K4J4E7WV.js.map → chunk-LO4UKPKX.js.map} +0 -0
- /package/dist/{chunk-6NQ4LSMS.js.map → chunk-MGZMC6WN.js.map} +0 -0
- /package/dist/{chunk-BMYZTZ5D.js.map → chunk-MY7B3HYC.js.map} +0 -0
- /package/dist/{chunk-NB3LRY4T.js.map → chunk-N4PYB235.js.map} +0 -0
- /package/dist/{chunk-S3YM4SZQ.js.map → chunk-N7QV3CMX.js.map} +0 -0
- /package/dist/{chunk-L6UZEAID.js.map → chunk-NJVR724O.js.map} +0 -0
- /package/dist/{chunk-VLLD4TOB.js.map → chunk-NMZVDRVP.js.map} +0 -0
- /package/dist/{chunk-P2WZHLUF.js.map → chunk-NOL7SEWN.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
- /package/dist/{chunk-6L3PBSAR.js.map → chunk-PMNLL2TL.js.map} +0 -0
- /package/dist/{chunk-VYCOJKSX.js.map → chunk-PO7NXNGC.js.map} +0 -0
- /package/dist/{chunk-4IIMMZZK.js.map → chunk-PULDSMCJ.js.map} +0 -0
- /package/dist/{chunk-HBBHL4YF.js.map → chunk-QOXNTKXB.js.map} +0 -0
- /package/dist/{chunk-CUCWX3MO.js.map → chunk-QZLCVO2V.js.map} +0 -0
- /package/dist/{chunk-NE7I2RCT.js.map → chunk-R3X2T2LP.js.map} +0 -0
- /package/dist/{chunk-S7GCT7NU.js.map → chunk-RBITPCMK.js.map} +0 -0
- /package/dist/{chunk-EVBI7S4J.js.map → chunk-RMDGAAZF.js.map} +0 -0
- /package/dist/{chunk-BJRAELDH.js.map → chunk-SCXURCVT.js.map} +0 -0
- /package/dist/{chunk-EQ6YOIKO.js.map → chunk-ST2RQUXC.js.map} +0 -0
- /package/dist/{chunk-CS52MFDV.js.map → chunk-SZG4WFAG.js.map} +0 -0
- /package/dist/{chunk-BFO3IR56.js.map → chunk-TPEZ2SHV.js.map} +0 -0
- /package/dist/{chunk-YRLHEEQT.js.map → chunk-U6VC6RP3.js.map} +0 -0
- /package/dist/{chunk-MOQPEFLN.js.map → chunk-UP75KL3L.js.map} +0 -0
- /package/dist/{chunk-W4AOR52H.js.map → chunk-UQDC3W77.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
- /package/dist/{chunk-D5F7TKMH.js.map → chunk-V4JZCTMK.js.map} +0 -0
- /package/dist/{chunk-MQJL6CDZ.js.map → chunk-VA4O3U7U.js.map} +0 -0
- /package/dist/{chunk-4CDYONWO.js.map → chunk-VDO32NLK.js.map} +0 -0
- /package/dist/{chunk-SOZUJCME.js.map → chunk-VHDKPGMG.js.map} +0 -0
- /package/dist/{chunk-QWW2JDDX.js.map → chunk-WNRTXEWW.js.map} +0 -0
- /package/dist/{chunk-NR2ASHBL.js.map → chunk-XOV7NRY5.js.map} +0 -0
- /package/dist/{chunk-KP7SANCF.js.map → chunk-YPI4RQWV.js.map} +0 -0
- /package/dist/{chunk-JKMHZF3Q.js.map → chunk-YSWJXOOL.js.map} +0 -0
- /package/dist/{chunk-LQG5O5EP.js.map → chunk-Z3O53WZR.js.map} +0 -0
- /package/dist/{chunk-QVS22WTO.js.map → chunk-ZG5DWWKK.js.map} +0 -0
- /package/dist/{graph-edge-decay-KQGS3L3B.js.map → embedding-generation-lock.js.map} +0 -0
- /package/dist/{resolution-DNX7FO7A.js.map → embedding-index-storage.js.map} +0 -0
- /package/dist/{first-start-migration-7OATHJKB.js.map → first-start-migration-GAT7G733.js.map} +0 -0
- /package/dist/{state-ITXJKXRE.js.map → graph-edge-decay-MKIRZ762.js.map} +0 -0
- /package/dist/{tui-55UJMJX7.js.map → offline-sync-changeset-generations.js.map} +0 -0
- /package/dist/{trace-ZFVRH4GO.js.map → trace-LXXCMZJA.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/console/state.ts"],"sourcesContent":["/**\n * Structured engine-state aggregator for the operator console (issue #688).\n *\n * This module is the data layer for the upcoming console TUI / HTTP /\n * MCP surfaces. It collects a point-in-time snapshot of the engine's\n * runtime state — buffer contents, extraction queue depth, recent\n * dedup decisions, the tail of the maintenance/observation ledger,\n * QMD probe status, and daemon metadata — into a single\n * JSON-serializable shape.\n *\n * Design contract:\n * - Each subsystem read is wrapped in try/catch so one failure\n * never crashes the whole snapshot. Failed reads return null /\n * empty values and append a string to `errors`.\n * - The output MUST be JSON-serializable so the CLI / HTTP / MCP\n * surfaces can pipe it directly through `JSON.stringify`.\n * - Read-only: this module never mutates orchestrator state.\n *\n * PR 1/3 of issue #688 wires only the data layer + a CLI flag. The\n * TUI (PR 2/3), HTTP `/console/state`, MCP `engram.console_state`,\n * SSE live updates, and trace replay (PR 3/3) land separately.\n */\n\nimport { promises as fs, createReadStream } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport path from \"node:path\";\n\n/**\n * Public state read off the orchestrator. We intentionally type this\n * as a permissive duck-typed shape rather than importing the concrete\n * `Orchestrator` class — the aggregator must tolerate missing fields\n * (e.g. tests that pass a stub) without throwing. Each accessor is\n * defensively read at call time.\n */\nexport interface ConsoleStateOrchestratorLike {\n config?: {\n memoryDir?: string;\n };\n buffer?: {\n getTurns?: (bufferKey?: string) => Array<{ content?: string }>;\n };\n qmd?: {\n debugStatus?: () => string;\n isAvailable?: () => boolean;\n isDaemonMode?: () => boolean;\n };\n /**\n * Optional console-specific read surface added by future slices.\n * The aggregator falls back to placeholders when these are absent\n * so PR 1/3 ships without coupling to private orchestrator state.\n */\n getConsoleExtractionQueueDepth?: () => number;\n getConsoleExtractionRecentVerdicts?: () => ReadonlyArray<ConsoleExtractionVerdict>;\n getConsoleDedupRecentDecisions?: () => ReadonlyArray<ConsoleDedupDecision>;\n /**\n * Process / daemon metadata. Hosts that run remnic as a long-lived\n * daemon (gateway, MCP server) can override these; otherwise the\n * aggregator falls back to `process.uptime()` and the package\n * version read from disk.\n */\n getConsoleDaemonInfo?: () => ConsoleDaemonInfo;\n /**\n * Optional faithfulness gate distribution (issue #1576). Returns the\n * per-orchestrator running counters when the gate is active, or undefined\n * when the gate is off (so the snapshot omits the block).\n */\n getConsoleFaithfulnessDistribution?: () => ConsoleFaithfulnessDistribution | undefined;\n}\n\nexport interface ConsoleBufferState {\n /** Number of turns currently held in the default buffer slot. */\n turnsCount: number;\n /** Approximate byte size of buffered content (UTF-8). */\n byteCount: number;\n}\n\nexport interface ConsoleExtractionVerdict {\n /** ISO-8601 timestamp the verdict was recorded. */\n ts: string;\n /** Verdict kind — typically \"accept\" | \"reject\" | \"defer\". */\n kind: string;\n /** Optional short reason from the judge or fallback. */\n reason?: string;\n}\n\nexport interface ConsoleExtractionQueueState {\n /** Number of pending extraction tasks queued in the orchestrator. */\n depth: number;\n /** Most recent judge verdicts, newest last. Capped at 25 entries. */\n recentVerdicts: ConsoleExtractionVerdict[];\n}\n\nexport interface ConsoleDedupDecision {\n /** ISO-8601 timestamp the decision was recorded. */\n ts: string;\n /** Outcome of the dedup check — \"duplicate\" | \"novel\" | etc. */\n decision: string;\n /** Optional content fingerprint (first 16 chars of a hash). */\n fingerprint?: string;\n /** Optional similarity score that drove the decision. */\n similarity?: number;\n}\n\nexport interface ConsoleMaintenanceLedgerEvent {\n /** ISO-8601 timestamp from the ledger row. */\n ts: string;\n /** Event category (e.g. \"EXTRACTION_JUDGE_VERDICT\"). */\n category: string;\n /** Compact one-line summary. */\n summary: string;\n}\n\nexport interface ConsoleQmdProbeState {\n /** Whether QMD is reachable via CLI or daemon. */\n available: boolean;\n /** Whether the daemon transport is in use. */\n daemonMode: boolean;\n /** Raw debug-status string from `qmd.debugStatus()`. */\n debug: string;\n}\n\nexport interface ConsoleDaemonInfo {\n /** Process uptime in milliseconds. */\n uptimeMs: number;\n /** Package / daemon version (best-effort). */\n version: string;\n}\n\n/**\n * Faithfulness gate verdict distribution (issue #1576). Surfaced via\n * console_state so `remnic doctor` can render how the gate is performing.\n */\nexport interface ConsoleFaithfulnessDistribution {\n entailed: number;\n contradicted: number;\n unsupported: number;\n unchecked: number;\n skippedNoSpan: number;\n}\n\nexport interface ConsoleStateSnapshot {\n /** ISO-8601 capture timestamp. */\n capturedAt: string;\n bufferState: ConsoleBufferState;\n extractionQueue: ConsoleExtractionQueueState;\n dedupRecent: ConsoleDedupDecision[];\n maintenanceLedgerTail: ConsoleMaintenanceLedgerEvent[];\n qmdProbe: ConsoleQmdProbeState;\n daemon: ConsoleDaemonInfo;\n /** Faithfulness gate distribution (issue #1576). Absent when the gate is off. */\n faithfulness?: ConsoleFaithfulnessDistribution;\n /**\n * Subsystem read errors. One entry per failed reader keyed by\n * subsystem name (e.g. `\"bufferState: ...\"`). An empty array means\n * every section was read cleanly.\n */\n errors: string[];\n}\n\nconst MAX_LEDGER_TAIL = 50;\nconst MAX_VERDICT_TAIL = 25;\nconst MAX_DEDUP_TAIL = 10;\n\n/**\n * Gather a `ConsoleStateSnapshot` from the orchestrator. Each\n * subsystem read is independent; a thrown error in one reader is\n * captured in `errors` and the corresponding section is filled with\n * empty / placeholder values.\n */\nexport async function gatherConsoleState(\n orchestrator: ConsoleStateOrchestratorLike,\n): Promise<ConsoleStateSnapshot> {\n const errors: string[] = [];\n const capturedAt = new Date().toISOString();\n\n const bufferState = readBufferState(orchestrator, errors);\n const extractionQueue = readExtractionQueue(orchestrator, errors);\n const dedupRecent = readDedupRecent(orchestrator, errors);\n const maintenanceLedgerTail = await readMaintenanceLedgerTail(\n orchestrator,\n errors,\n );\n const qmdProbe = readQmdProbe(orchestrator, errors);\n const daemon = readDaemonInfo(orchestrator, errors);\n\n // Faithfulness gate distribution (issue #1576). Optional — absent when\n // the gate is off or the orchestrator does not expose the accessor.\n let faithfulness: ConsoleFaithfulnessDistribution | undefined;\n try {\n if (typeof orchestrator.getConsoleFaithfulnessDistribution === \"function\") {\n faithfulness = orchestrator.getConsoleFaithfulnessDistribution();\n }\n } catch (err) {\n errors.push(`faithfulness: ${describeError(err)}`);\n }\n\n return {\n capturedAt,\n bufferState,\n extractionQueue,\n dedupRecent,\n maintenanceLedgerTail,\n qmdProbe,\n daemon,\n ...(faithfulness ? { faithfulness } : {}),\n errors,\n };\n}\n\nfunction readBufferState(\n orchestrator: ConsoleStateOrchestratorLike,\n errors: string[],\n): ConsoleBufferState {\n try {\n const getTurns = orchestrator.buffer?.getTurns;\n if (typeof getTurns !== \"function\") {\n return { turnsCount: 0, byteCount: 0 };\n }\n const turns = getTurns.call(orchestrator.buffer) ?? [];\n let byteCount = 0;\n for (const turn of turns) {\n const content = typeof turn?.content === \"string\" ? turn.content : \"\";\n byteCount += Buffer.byteLength(content, \"utf8\");\n }\n return { turnsCount: turns.length, byteCount };\n } catch (err) {\n errors.push(`bufferState: ${describeError(err)}`);\n return { turnsCount: 0, byteCount: 0 };\n }\n}\n\nfunction readExtractionQueue(\n orchestrator: ConsoleStateOrchestratorLike,\n errors: string[],\n): ConsoleExtractionQueueState {\n try {\n const depth =\n typeof orchestrator.getConsoleExtractionQueueDepth === \"function\"\n ? orchestrator.getConsoleExtractionQueueDepth()\n : 0;\n const verdicts =\n typeof orchestrator.getConsoleExtractionRecentVerdicts === \"function\"\n ? orchestrator.getConsoleExtractionRecentVerdicts()\n : [];\n const recentVerdicts = (verdicts ?? [])\n .slice(-MAX_VERDICT_TAIL)\n .map((v) => ({\n ts: typeof v?.ts === \"string\" ? v.ts : \"\",\n kind: typeof v?.kind === \"string\" ? v.kind : \"unknown\",\n ...(typeof v?.reason === \"string\" ? { reason: v.reason } : {}),\n }));\n return { depth: Number.isFinite(depth) ? depth : 0, recentVerdicts };\n } catch (err) {\n errors.push(`extractionQueue: ${describeError(err)}`);\n return { depth: 0, recentVerdicts: [] };\n }\n}\n\nfunction readDedupRecent(\n orchestrator: ConsoleStateOrchestratorLike,\n errors: string[],\n): ConsoleDedupDecision[] {\n try {\n // Cursor Medium: call directly on `orchestrator` to preserve the\n // `this` binding. Extracting the method into a local and invoking\n // it bare loses `this`, so a non-arrow implementation that\n // references `this` would fail at runtime. Mirrors how\n // `readExtractionQueue` and `readMaintenanceLedgerTail` invoke\n // their orchestrator methods.\n if (typeof orchestrator.getConsoleDedupRecentDecisions !== \"function\") return [];\n const raw = orchestrator.getConsoleDedupRecentDecisions() ?? [];\n return raw.slice(-MAX_DEDUP_TAIL).map((d) => ({\n ts: typeof d?.ts === \"string\" ? d.ts : \"\",\n decision: typeof d?.decision === \"string\" ? d.decision : \"unknown\",\n ...(typeof d?.fingerprint === \"string\"\n ? { fingerprint: d.fingerprint }\n : {}),\n ...(typeof d?.similarity === \"number\" && Number.isFinite(d.similarity)\n ? { similarity: d.similarity }\n : {}),\n }));\n } catch (err) {\n errors.push(`dedupRecent: ${describeError(err)}`);\n return [];\n }\n}\n\n/**\n * Stream a JSONL file line-by-line, parse each row, project it into a\n * console event, and keep only the `topN` most-recent items by `ts`\n * (descending). Memory is bounded at O(2 * topN) regardless of file\n * size — we sort-and-truncate whenever the working buffer grows past\n * `2 * topN`. This satisfies BOTH:\n *\n * 1. \"Don't load the whole file into memory\" — streaming + bounded\n * buffer means a multi-GB ledger never blows up RAM.\n * 2. \"Don't miss recent rows just because the file isn't sorted by\n * recency\" — `rebuilt-observations.jsonl` is written sorted by\n * `(sessionKey, hour)`, NOT by global ts, so a byte-tail-only\n * read silently dropped recent-but-lexicographically-earlier\n * sessions. Streaming top-N visits every row and keeps the\n * globally-most-recent ones.\n *\n * Returns an array sorted oldest-first within the top-N window. Rows\n * with no parseable `ts` are dropped (they would otherwise compare\n * undefined under any sort).\n */\nasync function streamLedgerTopN<T extends { ts: string }>(\n ledgerPath: string,\n topN: number,\n project: (parsed: Record<string, unknown>) => T | null,\n): Promise<T[]> {\n let stream: ReturnType<typeof createReadStream>;\n try {\n // `fs.access`-style probe so we can quietly return `[]` for\n // ENOENT without hitting the readline error path.\n await fs.stat(ledgerPath);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return [];\n throw err;\n }\n try {\n stream = createReadStream(ledgerPath, { encoding: \"utf-8\" });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return [];\n throw err;\n }\n const rl = createInterface({ input: stream, crlfDelay: Infinity });\n const cap = Math.max(1, topN) * 2;\n const buf: T[] = [];\n const truncate = () => {\n // Sort descending by ts, keep top-N, drop the rest.\n buf.sort((a, b) => {\n const aMs = Date.parse(a.ts);\n const bMs = Date.parse(b.ts);\n if (Number.isFinite(aMs) && Number.isFinite(bMs)) return bMs - aMs;\n if (Number.isFinite(aMs)) return -1;\n if (Number.isFinite(bMs)) return 1;\n return 0;\n });\n if (buf.length > topN) buf.length = topN;\n };\n try {\n for await (const line of rl) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n continue;\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n continue;\n }\n const event = project(parsed as Record<string, unknown>);\n if (event === null) continue;\n // Drop rows we couldn't even date-stamp — they can't be sorted\n // meaningfully against the rest of the corpus.\n if (!event.ts || !Number.isFinite(Date.parse(event.ts))) continue;\n buf.push(event);\n if (buf.length >= cap) truncate();\n }\n } finally {\n rl.close();\n stream.close();\n }\n truncate();\n return buf.reverse(); // oldest-first within the top-N window\n}\n\nfunction projectLedgerRow(p: Record<string, unknown>): ConsoleMaintenanceLedgerEvent | null {\n // The two canonical writers produce different row shapes:\n // - extraction-judge-telemetry: `{ts, verdictKind, reason, candidateCategory, ...}`\n // - rebuild-observations / migrate-observations:\n // `{sessionKey, hour, turnCount, userTurns, assistantTurns, rebuiltAt}`\n // Map both into the canonical {ts, category, summary} shape.\n const ts =\n typeof p.ts === \"string\" && p.ts.length > 0\n ? p.ts\n : typeof p.hour === \"string\" && p.hour.length > 0\n ? p.hour\n : typeof p.rebuiltAt === \"string\" && p.rebuiltAt.length > 0\n ? p.rebuiltAt\n : \"\";\n const category =\n typeof p.category === \"string\" && p.category.length > 0\n ? p.category\n : typeof p.verdictKind === \"string\"\n ? \"judge-verdict\"\n : typeof p.turnCount === \"number\"\n ? \"observation\"\n : \"unknown\";\n const summary = summarizeLedgerEvent(p);\n return { ts, category, summary };\n}\n\nasync function readMaintenanceLedgerTail(\n orchestrator: ConsoleStateOrchestratorLike,\n errors: string[],\n): Promise<ConsoleMaintenanceLedgerEvent[]> {\n try {\n const memoryDir = orchestrator.config?.memoryDir;\n if (!memoryDir || typeof memoryDir !== \"string\") return [];\n const ledgerDir = path.join(memoryDir, \"state\", \"observation-ledger\");\n // Codex review converged on two competing constraints:\n // 1. Don't load the whole file into memory (multi-GB ledgers\n // would OOM the console surface).\n // 2. Don't miss recent rows just because the file isn't sorted\n // by recency (`rebuilt-observations.jsonl` is sorted by\n // sessionKey/hour, so byte-tail reads silently drop recent\n // lexicographically-earlier sessions).\n // The right answer is a streaming bounded top-N: visit every row,\n // but keep only the MAX_LEDGER_TAIL most-recent in memory at any\n // time. Memory stays O(MAX_LEDGER_TAIL); recency ordering is\n // globally correct.\n const verdictsTopN = await streamLedgerTopN(\n path.join(ledgerDir, \"extraction-judge-verdicts.jsonl\"),\n MAX_LEDGER_TAIL,\n projectLedgerRow,\n );\n const rebuiltTopN = await streamLedgerTopN(\n path.join(ledgerDir, \"rebuilt-observations.jsonl\"),\n MAX_LEDGER_TAIL,\n projectLedgerRow,\n );\n // Merge the two source top-N windows and select the global\n // top-N. Both inputs already arrive oldest-first within their\n // respective windows; sort the merged set descending by ts and\n // take the most-recent MAX_LEDGER_TAIL.\n const events: ConsoleMaintenanceLedgerEvent[] = [...verdictsTopN, ...rebuiltTopN];\n if (events.length === 0) return [];\n events.sort((a, b) => {\n const aMs = Date.parse(a.ts);\n const bMs = Date.parse(b.ts);\n if (Number.isFinite(aMs) && Number.isFinite(bMs)) return bMs - aMs;\n if (Number.isFinite(aMs)) return -1;\n if (Number.isFinite(bMs)) return 1;\n return 0;\n });\n const tail = events.slice(0, MAX_LEDGER_TAIL);\n // Reverse so the caller sees oldest-first within the tail window.\n return tail.reverse();\n } catch (err) {\n errors.push(`maintenanceLedgerTail: ${describeError(err)}`);\n return [];\n }\n}\n\nfunction summarizeLedgerEvent(p: Record<string, unknown>): string {\n const parts: string[] = [];\n // Judge-verdict rows.\n if (typeof p.verdictKind === \"string\") parts.push(`verdict=${p.verdictKind}`);\n if (typeof p.reason === \"string\" && p.reason.length > 0) {\n const trimmed = p.reason.length > 80 ? `${p.reason.slice(0, 80)}…` : p.reason;\n parts.push(`reason=${trimmed}`);\n }\n if (typeof p.candidateCategory === \"string\") {\n parts.push(`cat=${p.candidateCategory}`);\n }\n // Canonical rebuilt-observations rows (codex P2).\n if (typeof p.sessionKey === \"string\" && p.sessionKey.length > 0) {\n parts.push(`session=${p.sessionKey}`);\n }\n if (typeof p.turnCount === \"number\" && Number.isFinite(p.turnCount)) {\n parts.push(`turns=${p.turnCount}`);\n }\n if (typeof p.userTurns === \"number\" && Number.isFinite(p.userTurns)) {\n parts.push(`u=${p.userTurns}`);\n }\n if (typeof p.assistantTurns === \"number\" && Number.isFinite(p.assistantTurns)) {\n parts.push(`a=${p.assistantTurns}`);\n }\n if (parts.length === 0) {\n return typeof p.category === \"string\" ? p.category : \"event\";\n }\n return parts.join(\" \");\n}\n\nfunction readQmdProbe(\n orchestrator: ConsoleStateOrchestratorLike,\n errors: string[],\n): ConsoleQmdProbeState {\n try {\n const qmd = orchestrator.qmd;\n if (!qmd) {\n return { available: false, daemonMode: false, debug: \"qmd unavailable\" };\n }\n const available =\n typeof qmd.isAvailable === \"function\" ? qmd.isAvailable() : false;\n const daemonMode =\n typeof qmd.isDaemonMode === \"function\" ? qmd.isDaemonMode() : false;\n const debug =\n typeof qmd.debugStatus === \"function\" ? qmd.debugStatus() : \"\";\n return { available, daemonMode, debug };\n } catch (err) {\n errors.push(`qmdProbe: ${describeError(err)}`);\n return { available: false, daemonMode: false, debug: \"\" };\n }\n}\n\nfunction readDaemonInfo(\n orchestrator: ConsoleStateOrchestratorLike,\n errors: string[],\n): ConsoleDaemonInfo {\n try {\n if (typeof orchestrator.getConsoleDaemonInfo === \"function\") {\n const info = orchestrator.getConsoleDaemonInfo();\n return {\n uptimeMs: Number.isFinite(info?.uptimeMs) ? info.uptimeMs : 0,\n version: typeof info?.version === \"string\" ? info.version : \"unknown\",\n };\n }\n // Fallback: process uptime + best-effort package version. This is\n // only meaningful when the orchestrator runs in the same process\n // as the CLI (which is the case for `remnic console --state-only`).\n const uptimeMs = Math.round((process.uptime?.() ?? 0) * 1000);\n return { uptimeMs, version: resolvePackageVersion() };\n } catch (err) {\n errors.push(`daemon: ${describeError(err)}`);\n return { uptimeMs: 0, version: \"unknown\" };\n }\n}\n\nfunction resolvePackageVersion(): string {\n // Best-effort, sync, never throws. We avoid importing package.json\n // statically because tsup bundles into a single file at runtime.\n try {\n const env = process.env?.REMNIC_VERSION;\n if (typeof env === \"string\" && env.length > 0) return env;\n } catch {\n // ignore\n }\n return \"unknown\";\n}\n\nfunction describeError(err: unknown): string {\n if (err instanceof Error) return err.message;\n try {\n return String(err);\n } catch {\n return \"unknown error\";\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/secure-store/secure-fs.ts"],"sourcesContent":["/**\n * Transparent file-level encryption for the secure-store module.\n *\n * Issue #690 (PR 3/4) — storage.ts integration layer.\n *\n * This module sits between the raw filesystem and StorageManager.\n * Every memory file is either:\n * - a plain UTF-8 text file (legacy, back-compat), or\n * - a REMNIC-ENC sealed file (AES-256-GCM, see format below).\n *\n * On-disk format\n * --------------\n * Encrypted files begin with a 9-byte magic header:\n *\n * REMNIC-ENC (7 ASCII bytes)\n * VER (1 byte, currently 0x01)\n * FLAGS (1 byte, reserved, must be 0x00)\n *\n * Followed immediately by a `seal()` envelope from `cipher.ts`:\n *\n * [VERSION:1][SALT:16][IV:12][AUTHTAG:16][CIPHERTEXT:...]\n *\n * The magic header makes encrypted files sniffable without attempting\n * a full `open()` call and gives operators a clear signal that the\n * file cannot be read by opening it in an editor.\n *\n * AAD\n * ---\n * The file path relative to the memory root is bound as Associated\n * Authenticated Data (AAD) on both encrypt and decrypt. This means\n * moving or renaming an encrypted file without re-encrypting it will\n * cause auth-tag failure on the next read — the file is tied to its\n * path. Callers that move files must re-encrypt them.\n *\n * Back-compat\n * -----------\n * `readMaybeEncryptedFile` transparently handles both formats: if the\n * file does NOT start with the magic bytes, it is returned as-is (plain\n * text). This lets an operator migrate incrementally: newly-written\n * files are encrypted while existing files continue to be read in plain\n * form until `migrateMemoryDirToEncrypted` is run.\n *\n * Naming: `secure-fs.ts` (not `vault-fs.ts`) — see `kdf.ts` naming note.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, randomUUID } from \"node:crypto\";\nimport { lstat, mkdir, open as openFile, readFile, readdir, rename, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport {\n AUTH_TAG_LENGTH,\n ENVELOPE_HEADER_SIZE,\n ENVELOPE_LAYOUT,\n ENVELOPE_SALT_LENGTH,\n ENVELOPE_VERSION,\n IV_LENGTH,\n generateSalt,\n open as openEnvelope,\n parseEnvelope,\n seal,\n} from \"./cipher.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport { isErrnoCode } from \"../utils/errno.js\";\n\n// ---------------------------------------------------------------------------\n// Error classes\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when a read is attempted but the keyring entry for this\n * store is absent (i.e. `secure-store unlock` has not been run\n * since the last daemon start).\n */\nexport class SecureStoreLockedError extends Error {\n constructor(message = \"secure-store is locked — run `remnic secure-store unlock` to decrypt\") {\n super(message);\n this.name = \"SecureStoreLockedError\";\n }\n}\n\n/**\n * Thrown when `open()` fails because the auth tag does not validate.\n * This covers both wrong-key and tampered-ciphertext scenarios —\n * intentionally indistinguishable from the caller's perspective.\n */\nexport class SecureStoreDecryptError extends Error {\n constructor(message = \"secure-store decryption failed — wrong key or tampered ciphertext\") {\n super(message);\n this.name = \"SecureStoreDecryptError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Magic header\n// ---------------------------------------------------------------------------\n\n/** Magic bytes: the ASCII string \"REMNIC-ENC\" (10 bytes). */\nexport const MAGIC_BYTES = Buffer.from(\"REMNIC-ENC\", \"ascii\");\n\n/** Current on-disk version byte. */\nexport const FILE_FORMAT_VERSION = 0x01;\n\n/** Reserved flags byte — must be 0x00. */\nexport const FILE_FORMAT_FLAGS = 0x00;\n\n/** Total size of the magic header prefix (magic + version + flags). */\nexport const MAGIC_HEADER_SIZE = MAGIC_BYTES.length + 2; // 12 bytes\n\n/**\n * Exact, deterministic bytes `writeMaybeEncryptedFile` adds to an encrypted\n * file beyond its plaintext body: the magic header prefix plus the fixed\n * envelope header (version + salt + IV + auth tag). AES-256-GCM ciphertext\n * matches the plaintext length and the state-file write path applies no\n * compression, so an encrypted state file is exactly `plaintext +\n * SECURE_STORE_ENVELOPE_OVERHEAD_BYTES` on disk. Compaction reserves this so an\n * encrypted ledger lands strictly below the reader's whole-file decrypt cap\n * (issue #2033).\n */\nexport const SECURE_STORE_ENVELOPE_OVERHEAD_BYTES = MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE;\n\n// ---------------------------------------------------------------------------\n// Detection\n// ---------------------------------------------------------------------------\n\n/**\n * Return true iff `buf` begins with the REMNIC-ENC magic header.\n * Does not validate the envelope; just identifies the format.\n */\nexport function isEncryptedFile(buf: Uint8Array): boolean {\n if (buf.length < MAGIC_HEADER_SIZE) return false;\n const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);\n return b.subarray(0, MAGIC_BYTES.length).equals(MAGIC_BYTES);\n}\n\ntype PositionedFileReader = {\n read: (buffer: Buffer, offset: number, length: number, position: number) => Promise<{ bytesRead: number }>;\n};\n\nasync function readPositionedPrefix(handle: PositionedFileReader, buffer: Buffer): Promise<number> {\n let bytesRead = 0;\n while (bytesRead < buffer.length) {\n const result = await handle.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);\n if (result.bytesRead === 0) break;\n bytesRead += result.bytesRead;\n }\n return bytesRead;\n}\n\n/**\n * True when the REGULAR file at `probePath` is encrypted at rest (its first\n * bytes are the REMNIC-ENC magic header). ENOENT resolves to false — an absent\n * file has nothing to probe. A symlink, FIFO, device, or any other non-regular\n * path is REFUSED with a throw rather than opened: opening a FIFO blocks until a\n * writer appears and a symlink would redirect the probe outside our tree. The\n * ledgers and per-event spills we manage are always regular files, so a\n * non-regular path is an anomaly the caller must treat as unsafe, never follow\n * (#2033). Shared by the CLI ledger recovery probe and the auto-compaction probe\n * so both enforce identical open safety.\n */\nexport async function probeEncryptedRegularFileHeader(probePath: string): Promise<boolean> {\n let entryStat;\n try {\n entryStat = await lstat(probePath);\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return false;\n throw err;\n }\n if (!entryStat.isFile()) {\n throw new Error(\n `refusing to probe non-regular state path ${probePath} ` +\n `(symlink/FIFO/device); opening it could block or escape containment (#2033)`\n );\n }\n const handle = await openFile(probePath, \"r\");\n try {\n const header = Buffer.alloc(MAGIC_HEADER_SIZE);\n const bytesRead = await readPositionedPrefix(handle, header);\n return isEncryptedFile(header.subarray(0, bytesRead));\n } finally {\n await handle.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Encrypt / decrypt file body\n// ---------------------------------------------------------------------------\n\n/**\n * Encrypt `plain` (UTF-8 content of a memory file) and return a\n * Buffer ready to write to disk.\n *\n * @param plain Plain-text file content (UTF-8 string or Buffer).\n * @param key 32-byte AES-256 key from the keyring.\n * @param aad Optional associated data — defaults to empty if omitted.\n * Callers should pass the file path relative to memoryDir\n * so the ciphertext is bound to its location.\n */\nexport function encryptFileBody(plain: string | Buffer, key: Buffer, aad?: Buffer): Buffer {\n const plainBuf = typeof plain === \"string\" ? Buffer.from(plain, \"utf8\") : plain;\n const salt = generateSalt();\n const envelope = seal(key, salt, plainBuf, aad ? { aad } : {});\n\n const header = Buffer.alloc(MAGIC_HEADER_SIZE);\n MAGIC_BYTES.copy(header, 0);\n header.writeUInt8(FILE_FORMAT_VERSION, MAGIC_BYTES.length);\n header.writeUInt8(FILE_FORMAT_FLAGS, MAGIC_BYTES.length + 1);\n\n return Buffer.concat([header, envelope]);\n}\n\n/**\n * Decrypt a buffer produced by `encryptFileBody` and return the\n * original UTF-8 content.\n *\n * Throws `SecureStoreDecryptError` on auth failure (wrong key or\n * tampered ciphertext). Throws a plain `Error` for structural problems\n * (truncated buffer, wrong magic, unsupported version).\n */\nexport function decryptFileBody(buf: Buffer, key: Buffer, aad?: Buffer): Buffer {\n if (!isEncryptedFile(buf)) {\n throw new Error(\"decryptFileBody: buffer does not start with REMNIC-ENC magic header\");\n }\n const version = buf.readUInt8(MAGIC_BYTES.length);\n if (version !== FILE_FORMAT_VERSION) {\n throw new Error(\n `decryptFileBody: unsupported file format version ${version} (this build supports ${FILE_FORMAT_VERSION})`\n );\n }\n const flags = buf.readUInt8(MAGIC_BYTES.length + 1);\n if (flags !== FILE_FORMAT_FLAGS) {\n throw new Error(`decryptFileBody: unknown flags byte 0x${flags.toString(16).padStart(2, \"0\")}`);\n }\n const envelope = buf.subarray(MAGIC_HEADER_SIZE);\n parseEnvelope(envelope);\n try {\n return openEnvelope(key, envelope, aad ? { aad } : {});\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new SecureStoreDecryptError(`secure-store decryption failed: ${msg}`);\n }\n}\n\nfunction buildHeaderAad(salt: Uint8Array): Buffer {\n const out = Buffer.alloc(1 + ENVELOPE_SALT_LENGTH);\n out.writeUInt8(ENVELOPE_VERSION, 0);\n Buffer.from(salt).copy(out, 1);\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Path → AAD helper\n// ---------------------------------------------------------------------------\n\n/**\n * Build the AAD buffer for a file at `filePath` relative to\n * `memoryDir`. The AAD binds the ciphertext to its path so a\n * file cannot be silently relocated without re-encryption.\n *\n * When `memoryDir` is supplied and `filePath` is absolute, the\n * relative sub-path is used. Otherwise `filePath` is used verbatim.\n */\nexport function filePathAad(filePath: string, memoryDir?: string): Buffer {\n let rel = filePath;\n if (memoryDir && path.isAbsolute(filePath)) {\n rel = path.relative(memoryDir, filePath);\n }\n return Buffer.from(rel, \"utf8\");\n}\n\n// ---------------------------------------------------------------------------\n// High-level read / write helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read a file from `filePath`.\n *\n * - If the file is plaintext (no magic header), return its content\n * as-is — back-compat with unencrypted stores.\n * - If the file is encrypted AND `key` is provided, decrypt and return\n * the plaintext content.\n * - If the file is encrypted AND `key` is null, throw\n * `SecureStoreLockedError`.\n *\n * @param filePath Absolute path to the file.\n * @param key 32-byte AES-256 key, or null when the store is locked.\n * @param memoryDir Memory root for path-bound AAD. Should be absolute.\n */\nexport async function readMaybeEncryptedFileBuffer(\n filePath: string,\n key: Buffer | null,\n memoryDir?: string\n): Promise<Buffer> {\n const buf = await readFile(filePath);\n if (!isEncryptedFile(buf)) {\n // Plain file — legacy or unencrypted store.\n return buf;\n }\n // Encrypted — key required.\n if (key === null) {\n throw new SecureStoreLockedError(\n `secure-store is locked — cannot read encrypted file at ${filePath}. Run \\`remnic secure-store unlock\\` to decrypt.`\n );\n }\n return decryptFileBodyForPath(buf, key, filePath, memoryDir);\n}\n\nexport async function readMaybeEncryptedFile(\n filePath: string,\n key: Buffer | null,\n memoryDir?: string\n): Promise<string> {\n return (await readMaybeEncryptedFileBuffer(filePath, key, memoryDir)).toString(\"utf8\");\n}\n\nexport interface WriteMaybeEncryptedFileOptions {\n /**\n * File mode bits. Default 0o600 (owner read/write only).\n * Applied only on create; existing files inherit their existing mode.\n */\n mode?: number;\n /**\n * If true, write atomically via a temp file + rename (CLAUDE.md gotcha #54).\n * Default true.\n */\n atomic?: boolean;\n}\n\n/**\n * Write `content` to `filePath`.\n *\n * - If `key` is provided and non-null, encrypt the content first.\n * - If `key` is null, write the content as plain UTF-8 (unencrypted store).\n *\n * Writes atomically: content is written to a unique temp file\n * first, then renamed into place (CLAUDE.md gotcha #54 — never delete\n * before write).\n */\nexport async function writeMaybeEncryptedFile(\n filePath: string,\n content: string | Buffer,\n key: Buffer | null,\n options: WriteMaybeEncryptedFileOptions = {},\n memoryDir?: string\n): Promise<void> {\n const { mode = 0o600, atomic = true } = options;\n await mkdir(path.dirname(filePath), { recursive: true });\n\n let data: Buffer | string;\n if (key !== null) {\n const aad = filePathAad(filePath, memoryDir);\n data = encryptFileBody(content, key, aad);\n } else {\n data = content;\n }\n\n if (atomic) {\n const tempPath = uniqueAtomicTempPath(filePath, \"tmp\");\n try {\n await writeFile(tempPath, data, { mode });\n await rename(tempPath, filePath);\n } catch (err) {\n // Best-effort cleanup of the temp file.\n try {\n await unlink(tempPath);\n } catch {\n // ignore\n }\n throw err;\n }\n } else {\n await writeFile(filePath, data, { mode });\n }\n}\n\nexport async function writeMaybeEncryptedFileFromChunks(\n filePath: string,\n chunks: AsyncIterable<Buffer>,\n key: Buffer | null,\n options: WriteMaybeEncryptedFileOptions = {},\n memoryDir?: string\n): Promise<void> {\n const { mode = 0o600, atomic = true } = options;\n await mkdir(path.dirname(filePath), { recursive: true });\n const writePath = atomic ? `${filePath}.tmp-${process.pid}-${Date.now()}` : filePath;\n let completed = false;\n try {\n const handle = await openFile(writePath, \"w\", mode);\n try {\n if (key !== null) {\n const salt = generateSalt();\n const iv = randomBytes(IV_LENGTH);\n const header = Buffer.alloc(MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE);\n MAGIC_BYTES.copy(header, 0);\n header.writeUInt8(FILE_FORMAT_VERSION, MAGIC_BYTES.length);\n header.writeUInt8(FILE_FORMAT_FLAGS, MAGIC_BYTES.length + 1);\n const envelopeOffset = MAGIC_HEADER_SIZE;\n header.writeUInt8(ENVELOPE_VERSION, envelopeOffset + ENVELOPE_LAYOUT.version);\n salt.copy(header, envelopeOffset + ENVELOPE_LAYOUT.salt);\n iv.copy(header, envelopeOffset + ENVELOPE_LAYOUT.iv);\n await handle.write(header);\n\n const cipher = createCipheriv(\"aes-256-gcm\", key, iv, { authTagLength: AUTH_TAG_LENGTH });\n const aad = filePathAad(filePath, memoryDir);\n cipher.setAAD(Buffer.concat([buildHeaderAad(salt), aad]));\n for await (const chunk of chunks) {\n if (chunk.length === 0) continue;\n const encrypted = cipher.update(chunk);\n if (encrypted.length > 0) await handle.write(encrypted);\n }\n const final = cipher.final();\n if (final.length > 0) await handle.write(final);\n const authTag = cipher.getAuthTag();\n await handle.write(authTag, 0, authTag.length, MAGIC_HEADER_SIZE + ENVELOPE_LAYOUT.authTag);\n } else {\n for await (const chunk of chunks) {\n if (chunk.length > 0) await handle.write(chunk);\n }\n }\n } finally {\n await handle.close();\n }\n if (atomic) {\n await rename(writePath, filePath);\n }\n completed = true;\n } finally {\n if (!completed && atomic) {\n await unlink(writePath).catch(() => {});\n }\n }\n}\n\n/**\n * Stream a file through the secure-store reader without buffering the whole\n * payload. Plaintext files are passed through unchanged; encrypted files are\n * authenticated and decrypted incrementally.\n */\nexport async function* readMaybeEncryptedFileFromChunks(\n filePath: string,\n key: Buffer | null,\n memoryDir?: string,\n chunkSize = 64 * 1024\n): AsyncIterable<Buffer> {\n const handle = await openFile(filePath, \"r\");\n const headerSize = MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE;\n try {\n const prefix = Buffer.alloc(headerSize);\n const prefixBytes = await readPositionedPrefix(handle, prefix);\n const prefixView = prefix.subarray(0, prefixBytes);\n if (!isEncryptedFile(prefixView)) {\n if (prefixBytes > 0) yield Buffer.from(prefixView);\n let position = prefixBytes;\n const buffer = Buffer.alloc(chunkSize);\n for (;;) {\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);\n if (bytesRead === 0) break;\n position += bytesRead;\n yield Buffer.from(buffer.subarray(0, bytesRead));\n }\n return;\n }\n if (prefixBytes < headerSize) {\n throw new Error(`encrypted file header is truncated: ${filePath}`);\n }\n if (key === null) {\n throw new SecureStoreLockedError(\n `secure-store is locked — cannot read encrypted file at ${filePath}. ` +\n \"Run `remnic secure-store unlock` to decrypt.\"\n );\n }\n const envelope = prefix.subarray(MAGIC_HEADER_SIZE);\n const version = envelope.readUInt8(ENVELOPE_LAYOUT.version);\n if (version !== ENVELOPE_VERSION) {\n throw new Error(`secure-store: unsupported envelope version ${version}`);\n }\n const salt = envelope.subarray(ENVELOPE_LAYOUT.salt, ENVELOPE_LAYOUT.salt + ENVELOPE_SALT_LENGTH);\n const iv = envelope.subarray(ENVELOPE_LAYOUT.iv, ENVELOPE_LAYOUT.iv + IV_LENGTH);\n const authTag = envelope.subarray(ENVELOPE_LAYOUT.authTag, ENVELOPE_LAYOUT.authTag + AUTH_TAG_LENGTH);\n const decipher = createDecipheriv(\"aes-256-gcm\", key, iv, { authTagLength: AUTH_TAG_LENGTH });\n decipher.setAAD(Buffer.concat([buildHeaderAad(salt), filePathAad(filePath, memoryDir)]));\n decipher.setAuthTag(authTag);\n let position = headerSize;\n const buffer = Buffer.alloc(chunkSize);\n try {\n for (;;) {\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);\n if (bytesRead === 0) break;\n position += bytesRead;\n const plain = decipher.update(buffer.subarray(0, bytesRead));\n if (plain.length > 0) yield plain;\n }\n const final = decipher.final();\n if (final.length > 0) yield final;\n } catch (err) {\n if (err instanceof SecureStoreDecryptError) throw err;\n const message = err instanceof Error ? err.message : String(err);\n throw new SecureStoreDecryptError(`secure-store decryption failed: ${message}`);\n }\n } finally {\n await handle.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Migration\n// ---------------------------------------------------------------------------\n\nexport interface MigrateResult {\n /** Number of files successfully encrypted. */\n encrypted: number;\n /** Number of files already encrypted (skipped). */\n skipped: number;\n /** Files that failed to encrypt (path → error message). */\n errors: Array<{ filePath: string; error: string }>;\n}\n\nexport interface DecryptResult {\n /** Number of files successfully decrypted back to plaintext. */\n decrypted: number;\n /** Number of files already plaintext (skipped). */\n skipped: number;\n /** Files that failed to decrypt (path → error message). */\n errors: Array<{ filePath: string; error: string }>;\n}\n\n/**\n * Walk `dir` recursively, find encryptable storage-managed files that are not\n * yet encrypted, and re-write them as encrypted files under `key`.\n *\n * Safety rules per CLAUDE.md gotchas #54 and #25:\n * 1. A page-version snapshot is taken (via `createVersion`) BEFORE\n * each overwrite so the plaintext version is preserved in history.\n * Since this module has no direct access to `page-versioning.ts`\n * internals, callers who have page-versioning configured should\n * pass `onBeforeEncrypt` to take the snapshot.\n * 2. The new encrypted content is written to a temp file first,\n * then renamed atomically — never deleted before written.\n * 3. If encryption of any file fails, the error is recorded and the\n * original file is left intact (partial migration is safe).\n *\n * @param dir Absolute path to the memory directory.\n * @param key 32-byte AES-256 key.\n * @param onBeforeEncrypt Optional callback invoked before encrypting\n * each file. Can be used to take page-version\n * snapshots. Errors here are non-fatal.\n */\nexport async function migrateMemoryDirToEncrypted(\n dir: string,\n key: Buffer,\n onBeforeEncrypt?: (filePath: string) => Promise<void>\n): Promise<MigrateResult> {\n const result: MigrateResult = { encrypted: 0, skipped: 0, errors: [] };\n\n const files = await collectEncryptableStorageFiles(dir);\n for (const filePath of files) {\n try {\n const buf = await readFile(filePath);\n if (isEncryptedFile(buf)) {\n result.skipped++;\n continue;\n }\n // Call optional pre-encryption hook (e.g. page-version snapshot).\n if (onBeforeEncrypt) {\n try {\n await onBeforeEncrypt(filePath);\n } catch {\n // Non-fatal — continue with encryption even if snapshot fails.\n }\n }\n const content = buf.toString(\"utf8\");\n const aad = filePathAad(filePath, dir);\n const encrypted = encryptFileBody(content, key, aad);\n\n // Atomic write: temp → rename (gotcha #54).\n const tempPath = uniqueAtomicTempPath(filePath, \"enc-tmp\");\n try {\n await writeFile(tempPath, encrypted, { mode: 0o600 });\n await rename(tempPath, filePath);\n result.encrypted++;\n } catch (writeErr) {\n // Clean up temp file, leave original intact.\n try {\n const { unlink } = await import(\"node:fs/promises\");\n await unlink(tempPath);\n } catch {\n // ignore\n }\n throw writeErr;\n }\n } catch (err) {\n result.errors.push({\n filePath,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return result;\n}\n\n/**\n * Walk `dir` recursively, find storage-managed encrypted files, and\n * re-write them as plaintext under the same paths.\n *\n * This is the reversible counterpart to {@link migrateMemoryDirToEncrypted}.\n * It only touches files under the same storage-managed roots, skips\n * plaintext files, skips symlinks, excludes `.secure-store/`, and writes\n * each plaintext replacement via temp-file + rename so a per-file failure\n * leaves the ciphertext intact.\n */\nexport async function decryptMemoryDirToPlaintext(dir: string, key: Buffer): Promise<DecryptResult> {\n const result: DecryptResult = { decrypted: 0, skipped: 0, errors: [] };\n\n const files = await collectStorageManagedFiles(dir, isDecryptableStoragePath);\n for (const filePath of files) {\n try {\n const buf = await readFile(filePath);\n if (!isEncryptedFile(buf)) {\n result.skipped++;\n continue;\n }\n\n const plaintext = decryptFileBodyForPath(buf, key, filePath, dir);\n const tempPath = uniqueAtomicTempPath(filePath, \"dec-tmp\");\n try {\n await writeFile(tempPath, plaintext, { mode: 0o600 });\n await rename(tempPath, filePath);\n result.decrypted++;\n } catch (writeErr) {\n try {\n await unlink(tempPath);\n } catch {\n // ignore cleanup errors; original ciphertext is still intact.\n }\n throw writeErr;\n }\n } catch (err) {\n result.errors.push({\n filePath,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction uniqueAtomicTempPath(filePath: string, label: string): string {\n return `${filePath}.${label}-${process.pid}-${Date.now()}-${randomUUID()}`;\n}\n\nfunction decryptFileBodyForPath(buf: Buffer, key: Buffer, filePath: string, memoryDir?: string): Buffer {\n const aad = filePathAad(filePath, memoryDir);\n try {\n return decryptFileBody(buf, key, aad);\n } catch (err) {\n if (!(err instanceof SecureStoreDecryptError)) {\n throw err;\n }\n const legacyRoot = legacyNamespaceAadRootForFile(filePath, memoryDir);\n if (legacyRoot) {\n try {\n return decryptFileBody(buf, key, filePathAad(filePath, legacyRoot));\n } catch {\n // Fall through to the namespace-reader fallback below.\n }\n }\n\n const topLevelRoot = topLevelAadRootForNamespaceReader(filePath, memoryDir);\n if (topLevelRoot) {\n try {\n return decryptFileBody(buf, key, filePathAad(filePath, topLevelRoot));\n } catch {\n // Preserve the caller-facing error from the canonical decrypt attempt.\n }\n }\n\n throw err;\n }\n}\n\nfunction topLevelAadRootForNamespaceReader(filePath: string, memoryDir?: string): string | null {\n if (!memoryDir || !path.isAbsolute(filePath)) return null;\n const resolvedMemoryDir = path.resolve(memoryDir);\n const rel = path.relative(resolvedMemoryDir, filePath);\n if (rel === \"\" || rel.startsWith(\"..\") || path.isAbsolute(rel)) return null;\n const parts = resolvedMemoryDir.split(path.sep);\n if (parts.length < 3 || parts.at(-2) !== \"namespaces\" || !parts.at(-1)) return null;\n const topLevelRoot = parts.slice(0, -2).join(path.sep) || path.sep;\n const topRel = path.relative(topLevelRoot, filePath);\n if (topRel === \"\" || topRel.startsWith(\"..\") || path.isAbsolute(topRel)) {\n return null;\n }\n const topParts = topRel.split(path.sep);\n if (topParts[0] !== \"namespaces\" || topParts[1] !== parts.at(-1)) {\n return null;\n }\n return topLevelRoot;\n}\n\nfunction legacyNamespaceAadRootForFile(filePath: string, memoryDir?: string): string | null {\n if (!memoryDir || !path.isAbsolute(filePath)) return null;\n const rel = path.relative(memoryDir, filePath);\n if (rel === \"\" || rel.startsWith(\"..\") || path.isAbsolute(rel)) return null;\n const parts = rel.split(path.sep);\n if (parts[0] === \"namespaces\" && parts.length >= 3 && parts[1]) {\n return path.join(memoryDir, \"namespaces\", parts[1]);\n }\n return null;\n}\n\n/**\n * Recursively collect files under `dir` that are read through the\n * storage-layer secure-store helpers, excluding symlinked entries and\n * `.secure-store/` metadata.\n */\nasync function collectEncryptableStorageFiles(dir: string, rootDir = dir): Promise<string[]> {\n return collectStorageManagedFiles(dir, isEncryptableStoragePath, rootDir);\n}\n\n/**\n * Recursively collect regular files under storage-managed roots, excluding\n * symlinked entries and `.secure-store/` metadata. This broader collector is\n * used by the decrypt/disable path so future encrypted sidecars can be\n * restored without requiring extension-specific logic.\n */\nasync function collectStorageManagedFiles(\n dir: string,\n includeFile: (filePath: string, rootDir: string) => boolean,\n rootDir = dir\n): Promise<string[]> {\n const results: string[] = [];\n let scanDir = dir;\n let scanRootDir = rootDir;\n if (path.resolve(dir) === path.resolve(rootDir)) {\n const normalizedRoot = await resolveStorageManagedRootForScan(dir);\n if (!normalizedRoot) return results;\n scanDir = normalizedRoot;\n scanRootDir = normalizedRoot;\n }\n let names: string[];\n try {\n names = await readdir(scanDir, { encoding: \"utf8\" });\n } catch {\n return results;\n }\n for (const name of names) {\n if (name.startsWith(\".secure-store\")) continue;\n const full = path.join(scanDir, name);\n let isDir = false;\n let isFile = false;\n try {\n const s = await lstat(full);\n if (s.isSymbolicLink()) continue;\n isDir = s.isDirectory();\n isFile = s.isFile();\n } catch {\n continue;\n }\n if (isDir) {\n const sub = await collectStorageManagedFiles(full, includeFile, scanRootDir);\n results.push(...sub);\n } else if (isFile && includeFile(full, scanRootDir)) {\n results.push(full);\n }\n }\n return results;\n}\n\nasync function resolveStorageManagedRootForScan(dir: string): Promise<string | null> {\n const lstatPath = normalizePathForLstat(dir);\n let stat: Awaited<ReturnType<typeof lstat>>;\n try {\n stat = await lstat(lstatPath);\n } catch (error) {\n if (isFsErrorWithCode(error, \"ENOENT\")) return null;\n throw error;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`secure-store migration root must not be a symlink: ${dir}`);\n }\n if (!stat.isDirectory()) {\n throw new Error(`secure-store migration root must be a directory: ${dir}`);\n }\n return lstatPath;\n}\n\nfunction normalizePathForLstat(filePath: string): string {\n return stripTrailingPathSeparators(path.normalize(filePath));\n}\n\nfunction isFsErrorWithCode(error: unknown, code: string): boolean {\n return typeof error === \"object\" && error !== null && (error as { code?: unknown }).code === code;\n}\n\nfunction stripTrailingPathSeparators(filePath: string): string {\n const root = path.parse(filePath).root;\n let end = filePath.length;\n while (\n end > root.length &&\n (filePath[end - 1] === path.sep || filePath[end - 1] === path.posix.sep || filePath[end - 1] === path.win32.sep)\n ) {\n end -= 1;\n }\n return end === filePath.length ? filePath : filePath.slice(0, end);\n}\n\nfunction isEncryptableStoragePath(filePath: string, rootDir: string): boolean {\n const rel = path.relative(rootDir, filePath);\n if (rel === \"\" || rel.startsWith(\"..\") || path.isAbsolute(rel)) return false;\n const normalized = normalizeStorageRelativePath(rel);\n if (normalized === \"profile.md\") return true;\n if (isEncryptableStateSidecar(normalized)) return true;\n if (isEncryptableSummarySidecar(normalized)) return true;\n const firstSegment = normalized.split(\"/\", 1)[0];\n return ENCRYPTABLE_MARKDOWN_STORAGE_ROOTS.has(firstSegment) && normalized.endsWith(\".md\");\n}\n\nfunction isDecryptableStoragePath(filePath: string, rootDir: string): boolean {\n if (isEncryptableStoragePath(filePath, rootDir)) return true;\n const rel = path.relative(rootDir, filePath);\n if (rel === \"\" || rel.startsWith(\"..\") || path.isAbsolute(rel)) return false;\n const normalized = normalizeStorageRelativePath(rel);\n const firstSegment = normalized.split(\"/\", 1)[0];\n return DECRYPTABLE_SIDECAR_ROOTS.has(firstSegment);\n}\n\nfunction normalizeStorageRelativePath(rel: string): string {\n const normalized = rel.split(path.sep).join(\"/\");\n const parts = normalized.split(\"/\");\n if (parts[0] === \"namespaces\" && parts.length >= 3) {\n return parts.slice(2).join(\"/\");\n }\n return normalized;\n}\n\n// Every recall MEMORY category directory (facts/ + the CATEGORY_DIR_MAP memory\n// dirs) must be encryptable at rest — #1546 routes decision/preference/moment/...\n// memories into their own dirs, so hardcoding only facts/corrections/procedures/\n// reasoning-traces would silently write those categories in plaintext on\n// encrypted stores. RECALL_FALLBACK_DIRS is the single source of truth for that\n// set; the extra non-category markdown roots (artifacts/archive/entities/\n// identity) are appended.\n//\n// questions/ is deliberately EXCLUDED (RECALL_FALLBACK_DIRS omits the non-memory\n// queue dirs): the question queue is written/resolved through plain readFile/\n// writeFile (StorageManager.writeQuestion/resolveQuestion), NOT the secure-file\n// helpers. Encrypting questions/ here would make migration seal those files,\n// after which resolveQuestion() would read ciphertext as UTF-8, fail to update\n// the frontmatter, and overwrite/corrupt the file while returning success\n// (codex #1563 review). Keeping questions/ plaintext preserves the queue's\n// existing behavior; #1546 does not change question encryption.\nconst ENCRYPTABLE_MARKDOWN_STORAGE_ROOTS = new Set<string>([\n ...RECALL_FALLBACK_DIRS,\n \"artifacts\",\n \"archive\",\n \"entities\",\n \"identity\",\n]);\n\nconst ENCRYPTABLE_STATE_SIDECARS = new Set([\n \"state/behavior-signals.jsonl\",\n \"state/buffer-surprise-ledger.jsonl\",\n \"state/buffer.json\",\n \"state/compression-guideline-draft-state.json\",\n \"state/compression-guideline-state.json\",\n \"state/compression-guidelines.draft.md\",\n \"state/compression-guidelines.md\",\n \"state/entity-synthesis-queue.json\",\n \"state/fact-hashes.txt\",\n \"state/memory-actions.jsonl\",\n \"state/memory-lifecycle-ledger.jsonl\",\n \"state/meta.json\",\n \"state/reextract-jobs.jsonl\",\n \"state/topics.json\",\n]);\n\nfunction isEncryptableStateSidecar(normalized: string): boolean {\n return ENCRYPTABLE_STATE_SIDECARS.has(normalized);\n}\n\nfunction isEncryptableSummarySidecar(normalized: string): boolean {\n return normalized.startsWith(\"summaries/\") && normalized.endsWith(\".json\");\n}\n\nconst DECRYPTABLE_SIDECAR_ROOTS = new Set([\"state\", \"indexes\", \"index\", \"provenance\"]);\n"],"mappings":"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","names":["position","buffer","unlink"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/embedding-fallback.ts"],"sourcesContent":["import path from \"node:path\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { log } from \"./logger.js\";\nimport {\n resolveMemoryLifecycleCapabilities,\n resolveLocalLlmCapabilities,\n} from \"./capabilities.js\";\nimport { readEnvVar } from \"./runtime/env.js\";\nimport type { PluginConfig } from \"./types.js\";\nimport { resolvePipelineProcessingCapabilities } from \"./capabilities.js\";\nimport {\n getHostEmbeddingProvider,\n type HostEmbeddingProvider,\n normalizeHostEmbeddingVector,\n} from \"./host-embedding-provider.js\";\n\ntype EmbeddingProviderType = \"openai\" | \"local\" | \"host\";\n\ntype ProviderConfig = {\n type: EmbeddingProviderType;\n model: string;\n endpoint?: string;\n headers?: Record<string, string>;\n hostProvider?: HostEmbeddingProvider;\n};\n\ntype EmbeddingIndexEntry = {\n vector: number[];\n path: string;\n};\n\ntype EmbeddingResult = {\n provider: ProviderConfig;\n vector: number[];\n};\n\ntype EmbeddingIndexFile = {\n version: 1;\n provider: EmbeddingProviderType;\n model: string;\n entries: Record<string, EmbeddingIndexEntry>;\n};\n\ntype EmbeddingIndexIdentity = Pick<EmbeddingIndexFile, \"provider\" | \"model\">;\ntype EmbeddingIndexComparable =\n | EmbeddingIndexIdentity\n | Pick<ProviderConfig, \"type\" | \"model\">;\n\nconst DEFAULT_OPENAI_MODEL = \"text-embedding-3-small\";\n\n/**\n * Thrown by `EmbeddingFallback.search()` (via `embed()`) when the embedding\n * backend is effectively unavailable on the lookup path — either because the\n * HTTP fetch exceeded its deadline OR because the endpoint returned a non-2xx\n * status code. Callers that need to distinguish a backend outage from \"no\n * candidates\" can `instanceof`-check against this class.\n *\n * Round 9 fix (Finding UZqB): previously a timeout returned null from embed(),\n * which caused search() to return [] silently. decideSemanticDedup then\n * classified the result as no_candidates instead of backend_unavailable, so\n * the per-batch batchBackendUnavailable short-circuit never activated and\n * batches of N facts each paid a full timeout roundtrip.\n *\n * Round 10 fix (Findings Ui1J + Ui1L): search() now only re-throws this error\n * when the caller explicitly passes `{ throwOnTimeout: true }`. Without that\n * flag search() catches it and returns [] instead, preserving fail-open\n * semantics for recall-path callers (searchEmbeddingFallback) that have no\n * try/catch. Only the semantic-dedup path (semanticDedupLookup) passes the\n * flag so it can still reach decideSemanticDedup's backend_unavailable branch.\n *\n * Round 11 fix (Finding Ur_J): `embed()` now also throws this error from the\n * lookup path when the HTTP response is non-2xx (e.g. 429, 500, 503). Without\n * this, repeated 5xx outages would each return null → [] → no_candidates and\n * subsequent facts in the same batch would all pay full roundtrips instead of\n * tripping the per-batch backend_unavailable short-circuit.\n *\n * The class name is kept for backward compatibility — `EmbeddingTimeoutError`\n * now signals \"lookup backend unavailable\" rather than strictly \"timed out\".\n */\nexport class EmbeddingTimeoutError extends Error {\n override readonly name = \"EmbeddingTimeoutError\" as const;\n constructor(message: string) {\n super(message);\n }\n}\n\nexport class EmbeddingProviderUnavailableError extends Error {\n override readonly name = \"EmbeddingProviderUnavailableError\" as const;\n constructor(message: string) {\n super(message);\n }\n}\n\nfunction isLookupBackendUnavailableError(err: unknown): boolean {\n return (\n err instanceof EmbeddingTimeoutError ||\n err instanceof EmbeddingProviderUnavailableError\n );\n}\n\n/**\n * Maximum time to wait for an embedding HTTP request on the LOOKUP/query\n * path before giving up.\n *\n * The write-time semantic dedup guard in orchestrator.persistExtraction()\n * blocks each candidate fact on an embedding lookup. If the embedding\n * endpoint hangs (degraded OpenAI, stalled local gateway, DNS timeout),\n * extraction would otherwise stall indefinitely — a single bad backend\n * could freeze the entire persist loop. Bounding the fetch here ensures\n * the decision path fails open (returns null) within a predictable window\n * and writes proceed as non-duplicates.\n *\n * Tests can override via REMNIC_EMBEDDING_FETCH_TIMEOUT_MS so they don't\n * have to wait the full default on hung-fetch assertions.\n *\n * Related: joshuaswarren/remnic#373, PR #399 P1/P2 review.\n */\nconst DEFAULT_EMBEDDING_LOOKUP_TIMEOUT_MS = 5000;\n\n/**\n * Maximum time to wait for an embedding HTTP request on the INDEX path.\n *\n * Indexing runs asynchronously after a memory has already been persisted\n * to disk. It does not block extraction or writes — it only updates the\n * embedding index used by later semantic dedup lookups. A slow local\n * CPU-backed embedding model can legitimately take tens of seconds per\n * call, so applying the short lookup timeout here silently dropped index\n * updates and caused later dedup lookups to miss recently persisted\n * memories. Use a much larger budget on this path.\n *\n * Tests can override via REMNIC_EMBEDDING_INDEX_TIMEOUT_MS.\n */\nconst DEFAULT_EMBEDDING_INDEX_TIMEOUT_MS = 120_000;\n\nfunction resolveEmbeddingLookupTimeoutMs(): number {\n const raw = readEnvVar(\"REMNIC_EMBEDDING_FETCH_TIMEOUT_MS\");\n if (raw) {\n const parsed = Number(raw);\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.floor(parsed);\n }\n }\n return DEFAULT_EMBEDDING_LOOKUP_TIMEOUT_MS;\n}\n\nfunction resolveEmbeddingIndexTimeoutMs(): number {\n const raw = readEnvVar(\"REMNIC_EMBEDDING_INDEX_TIMEOUT_MS\");\n if (raw) {\n const parsed = Number(raw);\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.floor(parsed);\n }\n }\n return DEFAULT_EMBEDDING_INDEX_TIMEOUT_MS;\n}\n\n/**\n * Options for the low-level embed() call.\n *\n * `mode` selects the timeout profile:\n * - \"lookup\" (default): bounded by the short lookup budget; fails open fast.\n * - \"index\": bounded by a much longer budget so slow backends can still\n * index newly persisted memories.\n */\nexport type EmbedMode = \"lookup\" | \"index\";\n\nexport class EmbeddingFallback {\n private readonly indexPath: string;\n private loaded: EmbeddingIndexFile | null = null;\n private mutationQueue: Promise<void> = Promise.resolve();\n\n constructor(private readonly config: PluginConfig) {\n this.indexPath = path.join(config.memoryDir, \"state\", \"embeddings.json\");\n }\n\n async isAvailable(): Promise<boolean> {\n return (await this.resolveProvider()) !== null;\n }\n\n /**\n * Embed an array of texts and return their embedding vectors.\n *\n * This is the public batch-embed interface used by semantic chunking\n * (Finding 1, PR #420 post-merge). Texts are grouped into batches of\n * `embeddingBatchSize` (from `semanticChunkingConfig`, default 32) and\n * each batch is dispatched concurrently via `Promise.all()`. This\n * preserves the semantic intent of `embeddingBatchSize` — without batching,\n * every text incurred a sequential HTTP round-trip, making the batch size\n * config ineffective. (PR #439 post-merge Finding 2.)\n *\n * If the provider is unavailable or any single embedding fails, the method\n * throws so the caller can fall back to recursive chunking.\n */\n async embedTexts(texts: string[]): Promise<number[][]> {\n const provider = await this.resolveProvider();\n if (!provider) {\n throw new Error(\"Embedding provider is not available\");\n }\n\n const batchSize = Math.max(\n 1,\n this.config.semanticChunkingConfig?.embeddingBatchSize ?? 32,\n );\n\n const vectors: number[][] = [];\n for (let i = 0; i < texts.length; i += batchSize) {\n const batch = texts.slice(i, i + batchSize);\n const batchResults = await Promise.all(\n batch.map((text) => this.embed(text, provider, { mode: \"lookup\" })),\n );\n for (const vec of batchResults) {\n if (!vec) {\n throw new Error(\"Embedding returned null for input text\");\n }\n vectors.push(vec);\n }\n }\n return vectors;\n }\n\n /**\n * Nearest-neighbor search against the embedding index.\n *\n * @param query The query string to embed and search for.\n * @param limit Max number of hits to return.\n * @param options Optional filters.\n * - `pathPrefix` Restrict candidates to entries whose indexed `path`\n * starts with this prefix (relative to `memoryDir`).\n * Used by the semantic dedup guard to scope lookups\n * to the target namespace so a high-similarity hit\n * from a different namespace can't suppress a write\n * in the target namespace. Default: no filter.\n * - `pathExcludePrefixes`\n * Exclude any entry whose indexed `path` starts with\n * any of these prefixes. Used for the default\n * namespace case: when the default namespace lives at\n * `memoryDir` root (legacy layout) we still want to\n * exclude `namespaces/<other>/…` entries.\n */\n async search(\n query: string,\n limit: number,\n options: {\n pathPrefix?: string;\n pathExcludePrefixes?: readonly string[];\n /**\n * When true, an `EmbeddingTimeoutError` from the embedding backend is\n * re-thrown to the caller. Use this on the semantic-dedup path so\n * `decideSemanticDedup`'s catch block can classify the result as\n * `reason=\"backend_unavailable\"` and activate the per-batch\n * short-circuit.\n *\n * When false (the default), a timeout is caught here and search()\n * returns [] instead — preserving fail-open semantics for the recall\n * path (`searchEmbeddingFallback`) which has no surrounding try/catch.\n * Without this gate a timed-out embedding request on the recall path\n * would propagate as an unhandled rejection and abort recall entirely.\n * (Round 10 fix, Findings Ui1J + Ui1L.)\n */\n throwOnTimeout?: boolean;\n } = {},\n ): Promise<Array<{ id: string; score: number; path: string }>> {\n const provider = await this.resolveProvider();\n if (!provider) return [];\n\n let queryResult = await this.embedForSearch(query, provider, options);\n if (!queryResult) return [];\n\n const diskIdentity = await this.readIndexIdentityFromDisk();\n if (diskIdentity && !sameIndexIdentity(diskIdentity, queryResult.provider)) {\n const diskProvider = await this.resolveFallbackProviderForIndexIdentity(diskIdentity);\n if (diskProvider) {\n const diskQueryResult = await this.embedForSearch(query, diskProvider, options);\n if (diskQueryResult && sameIndexIdentity(diskIdentity, diskQueryResult.provider)) {\n queryResult = diskQueryResult;\n } else {\n log.debug(\n `embedding fallback search skipped: preserved ${diskIdentity.provider}/${diskIdentity.model} index is unavailable for lookup`,\n );\n return [];\n }\n } else {\n log.debug(\n `embedding fallback search skipped: query provider ${queryResult.provider.type}/${queryResult.provider.model} does not match existing ${diskIdentity.provider}/${diskIdentity.model} index`,\n );\n return [];\n }\n }\n\n const index = await this.loadIndex(queryResult.provider);\n const ids = Object.keys(index.entries);\n if (ids.length === 0) return [];\n\n const includePrefix = normalizePathPrefix(options.pathPrefix);\n const excludePrefixes = (options.pathExcludePrefixes ?? [])\n .map((p) => normalizePathPrefix(p))\n .filter((p): p is string => typeof p === \"string\");\n\n const scored = ids\n .map((id) => {\n const entry = index.entries[id];\n return {\n id,\n path: entry.path,\n score: cosineSimilarity(queryResult.vector, entry.vector),\n };\n })\n .filter((r) => {\n if (!Number.isFinite(r.score)) return false;\n const normalized = normalizeEntryPath(r.path);\n if (includePrefix !== undefined && !normalized.startsWith(includePrefix)) {\n return false;\n }\n for (const excl of excludePrefixes) {\n if (normalized.startsWith(excl)) return false;\n }\n return true;\n })\n .sort((a, b) => b.score - a.score)\n .slice(0, Math.max(1, limit));\n\n return scored;\n }\n\n async indexFile(memoryId: string, content: string, filePath: string): Promise<void> {\n const provider = await this.resolveProvider();\n if (!provider) return;\n // Indexing is not on the write-critical path: a newly persisted memory\n // has already been written to disk by the time we reach this call. Use\n // the long \"index\" timeout so slow local embedding backends can still\n // add the entry to the index. Previously this used the short lookup\n // budget and silently dropped updates, leaving later dedup lookups\n // blind to the memory. Related: PR #399 P2.\n const result = await this.embedWithEffectiveProvider(content, provider, {\n mode: \"index\",\n });\n if (!result) return;\n\n await this.enqueueIndexMutation(async () => {\n const existing = await this.readIndexIdentityFromDisk();\n if (\n existing &&\n !sameIndexIdentity(existing, result.provider) &&\n !canReplaceIndexIdentity(existing, result.provider)\n ) {\n log.debug(\n `embedding fallback index update skipped: ${result.provider.type}/${result.provider.model} would replace existing ${existing.provider}/${existing.model} index`,\n );\n return;\n }\n const index = await this.loadIndex(result.provider);\n const relPath = toMemoryRelativePath(this.config.memoryDir, filePath);\n index.entries[memoryId] = {\n vector: result.vector,\n path: relPath,\n };\n await this.saveIndex(index);\n });\n }\n\n async removeFromIndex(memoryId: string): Promise<void> {\n const provider = await this.resolveProvider();\n if (!provider) return;\n\n await this.enqueueIndexMutation(async () => {\n const providers = [provider];\n const diskIdentity = await this.readIndexIdentityFromDisk();\n if (\n diskIdentity &&\n !providers.some((entry) => sameIndexIdentity(entry, diskIdentity))\n ) {\n providers.push(providerFromIndexIdentity(diskIdentity));\n }\n if (provider.type === \"host\") {\n const fallbackProvider = await this.resolveProvider({ includeHost: false });\n if (\n fallbackProvider &&\n !providers.some((entry) => sameIndexIdentity(entry, fallbackProvider))\n ) {\n providers.push(fallbackProvider);\n }\n }\n\n for (const indexProvider of providers) {\n const index = await this.loadIndex(indexProvider);\n if (!index.entries[memoryId]) continue;\n delete index.entries[memoryId];\n await this.saveIndex(index);\n }\n });\n }\n\n private enqueueIndexMutation<T>(mutation: () => Promise<T>): Promise<T> {\n const run = this.mutationQueue.catch(() => undefined).then(mutation);\n this.mutationQueue = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n private async resolveProvider(\n options: { includeHost?: boolean } = {},\n ): Promise<ProviderConfig | null> {\n if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return null;\n\n if (\n options.includeHost !== false &&\n resolvePipelineProcessingCapabilities(this.config).hostEmbeddingProvider !== false\n ) {\n const hostProvider = getHostEmbeddingProvider(this.config.memoryDir);\n if (hostProvider) {\n return {\n type: \"host\",\n model: hostProvider.model || hostProvider.id,\n hostProvider,\n };\n }\n }\n\n const preferred = this.config.embeddingFallbackProvider;\n const providers = preferred === \"auto\" ? [\"openai\", \"local\"] : [preferred];\n\n for (const p of providers) {\n if (p === \"openai\") {\n const provider = this.createOpenAiProvider();\n if (provider) return provider;\n }\n\n if (p === \"local\") {\n const provider = this.createLocalProvider();\n if (provider) return provider;\n }\n }\n\n return null;\n }\n\n private async resolveFallbackProviderForIndexIdentity(\n identity: EmbeddingIndexIdentity,\n ): Promise<ProviderConfig | null> {\n if (identity.provider === \"openai\") {\n const provider = this.createOpenAiProvider();\n return provider && sameIndexIdentity(provider, identity) ? provider : null;\n }\n if (identity.provider === \"local\") {\n const provider = this.createLocalProvider();\n return provider && sameIndexIdentity(provider, identity) ? provider : null;\n }\n return null;\n }\n\n private createOpenAiProvider(): ProviderConfig | null {\n if (!this.config.openaiApiKey) return null;\n const baseUrl = this.config.openaiBaseUrl ?? \"https://api.openai.com/v1\";\n return {\n type: \"openai\",\n model: DEFAULT_OPENAI_MODEL,\n endpoint: `${baseUrl.replace(/\\/$/, \"\")}/embeddings`,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.openaiApiKey}`,\n },\n };\n }\n\n private createLocalProvider(): ProviderConfig | null {\n if (!resolveLocalLlmCapabilities(this.config).localLlm || !this.config.localLlmUrl) return null;\n const base = this.config.localLlmUrl.replace(/\\/$/, \"\");\n const endpoint = /\\/v1$/i.test(base) ? `${base}/embeddings` : `${base}/v1/embeddings`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...(this.config.localLlmHeaders ?? {}),\n };\n if (this.config.localLlmApiKey && this.config.localLlmAuthHeader !== false) {\n headers.Authorization = `Bearer ${this.config.localLlmApiKey}`;\n }\n return {\n type: \"local\",\n model:\n this.config.embeddingFallbackModel ||\n this.config.localLlmModel ||\n DEFAULT_OPENAI_MODEL,\n endpoint,\n headers,\n };\n }\n\n private async embedForSearch(\n query: string,\n provider: ProviderConfig,\n options: { throwOnTimeout?: boolean } = {},\n ): Promise<EmbeddingResult | null> {\n try {\n return await this.embedWithEffectiveProvider(query, provider, {\n mode: \"lookup\",\n });\n } catch (err) {\n if (isLookupBackendUnavailableError(err)) {\n if (options.throwOnTimeout) {\n throw err;\n }\n // Fail-open: recall-path callers get an empty result rather than an\n // unhandled rejection that would abort recall entirely.\n log.debug(\"embedding fallback search: backend unavailable on lookup, returning [] (throwOnTimeout=false)\");\n return null;\n }\n throw err;\n }\n }\n\n private async embed(\n input: string,\n provider: ProviderConfig,\n options: { mode?: EmbedMode } = {},\n ): Promise<number[] | null> {\n const result = await this.embedWithEffectiveProvider(input, provider, options);\n return result?.vector ?? null;\n }\n\n private async embedWithEffectiveProvider(\n input: string,\n provider: ProviderConfig,\n options: { mode?: EmbedMode } = {},\n ): Promise<EmbeddingResult | null> {\n // Bound the fetch so a hung embedding endpoint cannot stall callers.\n // The lookup path uses a short budget (see DEFAULT_EMBEDDING_LOOKUP_TIMEOUT_MS\n // docblock) so semantic dedup fails open fast. The index path uses a\n // much longer budget because slow local backends (CPU embedding models)\n // otherwise drop index updates and blind later dedup lookups. See\n // DEFAULT_EMBEDDING_INDEX_TIMEOUT_MS docblock and PR #399 P2 review.\n const mode: EmbedMode = options.mode ?? \"lookup\";\n const timeoutMs =\n mode === \"index\"\n ? resolveEmbeddingIndexTimeoutMs()\n : resolveEmbeddingLookupTimeoutMs();\n if (provider.type === \"host\") {\n const vector = await this.embedWithHostProvider(input, provider, mode, timeoutMs);\n if (vector) return { provider, vector };\n const fallbackProvider = await this.resolveProvider({ includeHost: false });\n if (!fallbackProvider) {\n if (mode === \"lookup\") {\n throw new EmbeddingProviderUnavailableError(\n `host embedding provider unavailable (${provider.hostProvider?.id ?? provider.model})`,\n );\n }\n return null;\n }\n return this.embedWithEffectiveProvider(input, fallbackProvider, options);\n }\n if (!provider.endpoint || !provider.headers) return null;\n try {\n const res = await fetch(provider.endpoint, {\n method: \"POST\",\n headers: provider.headers,\n body: JSON.stringify({\n model: provider.model,\n input: input.slice(0, 8000),\n encoding_format: \"float\",\n }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!res.ok) {\n log.debug(`embedding fallback request failed: ${provider.type} ${res.status}`);\n // Round 11 fix (Finding Ur_J): on the LOOKUP path, a non-2xx response\n // means the embedding backend is effectively unavailable. Throw the\n // tagged error so search() (when called with throwOnTimeout) propagates\n // to decideSemanticDedup's backend_unavailable branch, activating the\n // per-batch short-circuit. Without this, repeated 429/5xx responses\n // would silently return [] for every fact in the batch.\n //\n // On the INDEX path a non-2xx is non-fatal (the memory is already\n // persisted; index update can be skipped) — return null there.\n if (mode === \"lookup\") {\n throw new EmbeddingTimeoutError(\n `embedding backend returned ${res.status} (${provider.type})`,\n );\n }\n return null;\n }\n const payload = (await res.json()) as any;\n const vector = payload?.data?.[0]?.embedding;\n if (!Array.isArray(vector)) return null;\n const normalized = vector\n .map((n: unknown) => Number(n))\n .filter((n: number) => Number.isFinite(n));\n return normalized.length > 0 ? { provider, vector: normalized } : null;\n } catch (err) {\n // Round 11 (Finding Ur_J): the !res.ok branch above throws\n // EmbeddingTimeoutError directly. Re-throw it here so the catch does\n // not swallow our own intentional signal back into a null return.\n if (isLookupBackendUnavailableError(err)) {\n throw err;\n }\n // AbortSignal.timeout throws a DOMException with name \"TimeoutError\";\n // surface at warn level so operators can distinguish slow backends from\n // generic errors.\n const isTimeout =\n err instanceof Error &&\n (err.name === \"TimeoutError\" || err.name === \"AbortError\");\n if (isTimeout) {\n log.warn(\n `embedding fallback fetch timed out after ${timeoutMs}ms (${provider.type}, mode=${mode})`,\n );\n // Round 9 fix (Finding UZqB): on the LOOKUP path a timeout means the\n // embedding backend is effectively unavailable — re-throw so that\n // search() propagates the error to semanticDedupLookup, which lets it\n // reach decideSemanticDedup's catch block and return\n // reason=\"backend_unavailable\". Without this, search() would silently\n // return [] and the per-batch batchBackendUnavailable flag would never\n // flip, causing subsequent facts in the same batch to each pay a full\n // timeout roundtrip (N × timeout instead of 1 × timeout).\n //\n // On the INDEX path a timeout is not fatal (the memory is already\n // persisted; index update can be skipped) — return null there so\n // indexFile() stays non-blocking.\n if (mode === \"lookup\") {\n throw new EmbeddingTimeoutError(\n `embedding backend timed out after ${timeoutMs}ms (${provider.type})`,\n );\n }\n } else {\n // Round 12 fix (PR #399 thread PRRT_kwDORJXyws56U6Gi): non-timeout\n // transport failures (ECONNREFUSED, DNS errors, TLS failures) are just\n // as fatal as timeouts on the LOOKUP path — the embedding backend is\n // effectively unreachable. Throw EmbeddingTimeoutError so that\n // search() (when called with throwOnTimeout:true) propagates the error\n // to decideSemanticDedup's backend_unavailable branch, activating the\n // per-batch short-circuit. Without this, each fact in the batch would\n // pay a full ECONNREFUSED roundtrip and return null → [] → no_candidates,\n // preventing batchBackendUnavailable from ever being set.\n //\n // On the INDEX path a transport failure is non-fatal — the memory is\n // already persisted; index update can be safely skipped.\n if (mode === \"lookup\") {\n log.warn(\n `embedding fallback transport error on lookup path (${provider.type}): ${err}`,\n );\n throw new EmbeddingTimeoutError(\n `embedding backend transport failure (${provider.type}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n log.debug(`embedding fallback error: ${err}`);\n }\n return null;\n }\n }\n\n private async embedWithHostProvider(\n input: string,\n provider: ProviderConfig,\n mode: EmbedMode,\n timeoutMs: number,\n ): Promise<number[] | null> {\n const hostProvider = provider.hostProvider;\n if (!hostProvider) return null;\n try {\n const vector = await hostProvider.embed(input.slice(0, 8000), {\n signal: AbortSignal.timeout(timeoutMs),\n inputType: mode === \"lookup\" ? \"query\" : \"document\",\n });\n return normalizeHostEmbeddingVector(vector);\n } catch (err) {\n log.debug(`host embedding provider error: ${hostProvider.id}: ${err}`);\n return null;\n }\n }\n\n private async loadIndex(provider: ProviderConfig): Promise<EmbeddingIndexFile> {\n if (this.loaded && this.loaded.provider === provider.type && this.loaded.model === provider.model) {\n return this.loaded;\n }\n\n try {\n const raw = await readFile(this.indexPath, \"utf-8\");\n const parsed = JSON.parse(raw) as EmbeddingIndexFile;\n if (\n parsed &&\n parsed.version === 1 &&\n parsed.provider === provider.type &&\n parsed.model === provider.model &&\n parsed.entries &&\n typeof parsed.entries === \"object\"\n ) {\n this.loaded = {\n version: 1,\n provider: provider.type,\n model: provider.model,\n entries: parsed.entries,\n };\n return this.loaded;\n }\n } catch {\n // ignore and create a new index\n }\n\n this.loaded = {\n version: 1,\n provider: provider.type,\n model: provider.model,\n entries: {},\n };\n return this.loaded;\n }\n\n private async readIndexIdentityFromDisk(): Promise<EmbeddingIndexIdentity | null> {\n try {\n const raw = await readFile(this.indexPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<EmbeddingIndexFile> | null;\n if (\n parsed &&\n parsed.version === 1 &&\n (parsed.provider === \"openai\" ||\n parsed.provider === \"local\" ||\n parsed.provider === \"host\") &&\n typeof parsed.model === \"string\" &&\n parsed.model.length > 0\n ) {\n return {\n provider: parsed.provider,\n model: parsed.model,\n };\n }\n } catch {\n // Missing or invalid indexes are treated as absent; loadIndex() owns\n // creating a fresh file when it is safe to write.\n }\n return null;\n }\n\n private async saveIndex(index: EmbeddingIndexFile): Promise<void> {\n const dir = path.dirname(this.indexPath);\n await mkdir(dir, { recursive: true });\n const tempPath = path.join(\n dir,\n `.embeddings.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,\n );\n try {\n await writeFile(tempPath, JSON.stringify(index), \"utf-8\");\n await rename(tempPath, this.indexPath);\n } catch (err) {\n await rm(tempPath, { force: true }).catch(() => undefined);\n throw err;\n }\n this.loaded = index;\n }\n}\n\nfunction toMemoryRelativePath(memoryDir: string, filePath: string): string {\n if (!path.isAbsolute(filePath)) return filePath;\n const rel = path.relative(memoryDir, filePath);\n return rel.startsWith(\"..\") ? filePath : rel;\n}\n\n/**\n * Normalize an index entry path to forward-slashes for stable prefix\n * comparison. Entries are stored as `path.relative(memoryDir, …)` output,\n * which on Windows uses back-slashes. Normalize both sides so prefix\n * matching is OS-independent.\n *\n * Also strip a leading `./` so this helper's output is symmetric with\n * `normalizePathPrefix` below. `toMemoryRelativePath` is a pass-through for\n * non-absolute filePath inputs, so an index entry could legitimately carry a\n * stored path like `\"./namespaces/alpha/facts/f.md\"`. Without this strip, a\n * caller-supplied prefix `\"./namespaces/alpha\"` (which `normalizePathPrefix`\n * rewrites to `\"namespaces/alpha/\"`) would silently miss that entry and\n * namespace-scoped dedup would either let a near-duplicate through or fail\n * to exclude a cross-namespace hit.\n */\nfunction normalizeEntryPath(p: string): string {\n let out = p.replace(/\\\\/g, \"/\");\n if (out.startsWith(\"./\")) out = out.slice(2);\n return out;\n}\n\n/**\n * Normalize a caller-supplied path prefix:\n * - Return `undefined` for nullish/empty input (no filter).\n * - Replace back-slashes with forward-slashes.\n * - Strip a leading `./`.\n * - Ensure a trailing `/` so `\"namespaces/a\"` doesn't accidentally match\n * `\"namespaces/another/…\"`.\n */\nfunction normalizePathPrefix(prefix: string | undefined): string | undefined {\n if (prefix === undefined || prefix === null) return undefined;\n let p = String(prefix).replace(/\\\\/g, \"/\");\n if (p.startsWith(\"./\")) p = p.slice(2);\n if (p.length === 0) return undefined;\n if (!p.endsWith(\"/\")) p = `${p}/`;\n return p;\n}\n\nfunction sameIndexIdentity(\n left: EmbeddingIndexComparable,\n right: EmbeddingIndexComparable,\n): boolean {\n return indexIdentityProvider(left) === indexIdentityProvider(right) && left.model === right.model;\n}\n\nfunction indexIdentityProvider(identity: EmbeddingIndexComparable): EmbeddingProviderType {\n return \"provider\" in identity ? identity.provider : identity.type;\n}\n\nfunction canReplaceIndexIdentity(\n existing: EmbeddingIndexIdentity,\n replacement: Pick<ProviderConfig, \"type\" | \"model\">,\n): boolean {\n return existing.provider === \"host\" && !sameIndexIdentity(existing, replacement);\n}\n\nfunction providerFromIndexIdentity(identity: EmbeddingIndexIdentity): ProviderConfig {\n return {\n type: identity.provider,\n model: identity.model,\n };\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n const n = Math.min(a.length, b.length);\n if (n === 0) return 0;\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < n; i++) {\n const av = a[i] ?? 0;\n const bv = b[i] ?? 0;\n dot += av * bv;\n normA += av * av;\n normB += bv * bv;\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n return dot / denom;\n}\n"],"mappings":"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","names":[]}
|