@remnic/core 10.0.91 → 10.0.93
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 +334 -61
- package/dist/access-authorization-probe.js +2 -283
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +1 -282
- package/dist/access-cli.js +288 -304
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.js +3 -3
- package/dist/access-extraction-force-flush.js +334 -61
- package/dist/access-http-briefing.js +334 -62
- package/dist/access-http-query.js +334 -63
- package/dist/access-http.js +332 -114
- package/dist/access-identity-continuity-surface.js +334 -61
- package/dist/access-lcm-surface.js +334 -61
- package/dist/access-mcp-frames.d.ts +20 -6
- package/dist/access-mcp-frames.d.ts.map +1 -1
- package/dist/access-mcp-frames.js +1 -1
- package/dist/access-mcp.d.ts +5 -0
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +332 -89
- package/dist/access-namespace-preflight.js +2 -282
- package/dist/access-observe-transcript.js +3 -3
- package/dist/access-observe-write-category.js +6 -6
- package/dist/access-observe-write-surface.js +334 -61
- package/dist/access-offline-manifest.js +12 -11
- package/dist/access-operations-batch.js +332 -66
- package/dist/access-operations.js +332 -75
- package/dist/access-recall-concurrency.js +334 -61
- package/dist/access-recall-diagnostics.js +6 -5
- package/dist/access-recall-response.js +334 -61
- package/dist/access-recall-surface.js +334 -61
- package/dist/access-schema.js +2 -2
- package/dist/access-service.d.ts +1 -0
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +337 -64
- package/dist/access-surface-catalog.js +6 -283
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-context-transform.js +1 -1
- package/dist/active-memory-bridge.js +3 -2
- package/dist/active-recall.js +36 -37
- package/dist/active-recall.js.map +1 -1
- package/dist/activity/journal-extract.d.ts +13 -1
- package/dist/activity/journal-extract.d.ts.map +1 -1
- package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
- package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
- package/dist/briefing.js +20 -20
- package/dist/buffer.js +16 -16
- package/dist/calibration.js +10 -10
- package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
- package/dist/causal-consolidation.js +23 -23
- package/dist/{chunk-QL7RHYK6.js → chunk-2LMA22UU.js} +5 -5
- package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
- package/dist/{chunk-RSK2WIEM.js → chunk-2YODOG2O.js} +2 -2
- package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
- package/dist/{chunk-OFVCFKDH.js → chunk-3CYOCE6U.js} +3 -3
- package/dist/{chunk-6HM6HARE.js → chunk-3FBAEPCU.js} +2 -2
- package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
- package/dist/{chunk-V4XE263I.js → chunk-43AA5FVD.js} +1 -1
- package/dist/chunk-43AA5FVD.js.map +1 -0
- package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
- package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
- package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
- package/dist/{chunk-PUEO37QG.js → chunk-5YT2IIFX.js} +729 -17
- package/dist/chunk-5YT2IIFX.js.map +1 -0
- package/dist/{chunk-P5VZSSYL.js → chunk-622V2AZA.js} +233 -40
- package/dist/chunk-622V2AZA.js.map +1 -0
- package/dist/{chunk-3UMDRGFS.js → chunk-65HWAQ4F.js} +3 -3
- package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
- package/dist/chunk-A2WFUMUN.js +86 -0
- package/dist/chunk-A2WFUMUN.js.map +1 -0
- package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
- package/dist/chunk-AHA5VQGP.js +117 -0
- package/dist/chunk-AHA5VQGP.js.map +1 -0
- package/dist/{chunk-AOFEMUHG.js → chunk-ATG2PQ4V.js} +56 -5
- package/dist/chunk-ATG2PQ4V.js.map +1 -0
- package/dist/{chunk-63O7LVMN.js → chunk-B33YWUJV.js} +3 -3
- package/dist/{chunk-QVMOCJRV.js → chunk-B377OKUP.js} +2 -2
- package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
- package/dist/chunk-B3DR2JBZ.js.map +1 -0
- package/dist/{chunk-ZT6WPSII.js → chunk-BGHCSLE5.js} +2 -2
- package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
- package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
- package/dist/{chunk-EV76IZ37.js → chunk-BTI4C4MA.js} +2 -2
- package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
- package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
- package/dist/{chunk-3XIWW4TB.js → chunk-CGJYL633.js} +2 -2
- package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
- package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
- package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
- package/dist/{chunk-RO7RUBLD.js → chunk-EFL24KOI.js} +2 -2
- package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
- package/dist/{chunk-Z4Q6USKJ.js → chunk-ETBVCRVL.js} +4 -4
- package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
- package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
- package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
- package/dist/chunk-FA7SLQ5X.js.map +1 -0
- package/dist/{chunk-SHUBTAY2.js → chunk-FMKILENU.js} +14 -14
- package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
- package/dist/{chunk-J4MMPQ7Q.js → chunk-GQXOBKPL.js} +7 -9
- package/dist/{chunk-J4MMPQ7Q.js.map → chunk-GQXOBKPL.js.map} +1 -1
- package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
- package/dist/chunk-HLUHEMN4.js.map +1 -0
- package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
- package/dist/chunk-JTSSSR4L.js +359 -0
- package/dist/chunk-JTSSSR4L.js.map +1 -0
- package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
- package/dist/chunk-JVBAPAWZ.js.map +1 -0
- package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
- package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
- package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
- package/dist/chunk-LLPMMMWG.js.map +1 -0
- package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
- package/dist/{chunk-JC36FPQ5.js → chunk-LPV72KRQ.js} +2 -2
- package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
- package/dist/{chunk-GGRRF4PZ.js → chunk-MDPT6QFK.js} +1 -1
- package/dist/{chunk-GGRRF4PZ.js.map → chunk-MDPT6QFK.js.map} +1 -1
- package/dist/{chunk-VPQFK63H.js → chunk-MH632KPO.js} +2 -2
- package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
- package/dist/chunk-MLAMXQAW.js.map +1 -0
- package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
- package/dist/{chunk-ZZ67UTQX.js → chunk-MUX7IL77.js} +2 -2
- package/dist/{chunk-RYWROSL5.js → chunk-NAQ4A5IX.js} +7 -7
- package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
- package/dist/{chunk-ADYP7HXE.js → chunk-NHVAX7MH.js} +3 -3
- package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
- package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
- package/dist/chunk-NS7U4ZYU.js.map +1 -0
- package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
- package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
- package/dist/{chunk-7L3TODI7.js → chunk-OJXJWNFY.js} +2 -2
- package/dist/{chunk-66L5ZRJC.js → chunk-P3WRNWWM.js} +157 -4
- package/dist/chunk-P3WRNWWM.js.map +1 -0
- package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
- package/dist/chunk-PC5DRBGD.js.map +1 -0
- package/dist/{chunk-EURPSF4Y.js → chunk-PH5ZQM35.js} +2 -2
- package/dist/{chunk-BMWMTAQF.js → chunk-PRAUN6PN.js} +3 -3
- package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
- package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
- package/dist/{chunk-AH637HZ5.js → chunk-QDESY76F.js} +3 -3
- package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
- package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
- package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
- package/dist/{chunk-EWJZZDRG.js → chunk-R4BBGPI2.js} +219 -219
- package/dist/chunk-R4BBGPI2.js.map +1 -0
- package/dist/{chunk-LJQFJF4J.js → chunk-RM2YUFH6.js} +6 -6
- package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
- package/dist/chunk-RQFXZYTF.js.map +1 -0
- package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
- package/dist/chunk-S3S72HRM.js.map +1 -0
- package/dist/{chunk-6QMWECO5.js → chunk-SGQPR5SX.js} +7 -7
- package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
- package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
- package/dist/{chunk-5UCMYPVF.js → chunk-SVT5IA2H.js} +2 -2
- package/dist/{chunk-HVKUCT3W.js → chunk-T4KY6UYF.js} +5 -3
- package/dist/{chunk-HVKUCT3W.js.map → chunk-T4KY6UYF.js.map} +1 -1
- package/dist/{chunk-FRSHF6FU.js → chunk-TJ4F6H4H.js} +3 -3
- package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
- package/dist/{chunk-NJJMVS5Y.js → chunk-TRQE3RYR.js} +5 -5
- package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
- package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
- package/dist/{chunk-YS2GHSWN.js → chunk-UBEKET2V.js} +389 -39
- package/dist/chunk-UBEKET2V.js.map +1 -0
- package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
- package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
- package/dist/{chunk-KZSGBWOJ.js → chunk-V2KGDESV.js} +2 -2
- package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
- package/dist/{chunk-BCCG7VWM.js → chunk-V65H7MI6.js} +396 -13
- package/dist/chunk-V65H7MI6.js.map +1 -0
- package/dist/chunk-W54Z2MOP.js +4500 -0
- package/dist/chunk-W54Z2MOP.js.map +1 -0
- package/dist/{chunk-N74SGESS.js → chunk-WKSQTMU7.js} +2 -2
- package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
- package/dist/{chunk-WWK6J6SE.js → chunk-WQURALGV.js} +4996 -2748
- package/dist/chunk-WQURALGV.js.map +1 -0
- package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
- package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
- package/dist/{chunk-GNXGJBCA.js → chunk-XIYLWEXC.js} +2 -2
- package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
- package/dist/{chunk-7O2MC42R.js → chunk-XNRLRNQF.js} +2 -2
- package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
- package/dist/{chunk-JBCCWVU2.js → chunk-XRDYRWZI.js} +2 -2
- package/dist/chunk-XRWWCW4H.js +102 -0
- package/dist/chunk-XRWWCW4H.js.map +1 -0
- package/dist/chunk-YKJBSMMP.js +67137 -0
- package/dist/chunk-YKJBSMMP.js.map +1 -0
- package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
- package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
- package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
- package/dist/{chunk-KEPXC4KN.js → chunk-ZSE774RV.js} +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +305 -187
- package/dist/coding/export-okf-codegraph.js +29 -29
- package/dist/compounding/engine.js +23 -24
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +36 -37
- package/dist/connectors/codex-materialize-runner.js +20 -20
- package/dist/connectors/index.js +21 -21
- package/dist/connectors/live/framework.d.ts +7 -0
- package/dist/connectors/live/framework.d.ts.map +1 -1
- package/dist/connectors/live/google-drive.d.ts +17 -3
- package/dist/connectors/live/google-drive.d.ts.map +1 -1
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +5 -5
- package/dist/contradiction/index.js +7 -6
- package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
- package/dist/convergence-refresh.js +22 -22
- package/dist/corpus-watermark.js +23 -23
- package/dist/deep-recall-http-glue.js +2 -2
- package/dist/deep-recall-policy-llm.js +4 -4
- package/dist/direct-answer-wiring.js +2 -2
- package/dist/embedding-fallback.js +1 -1
- package/dist/entity-retrieval.js +21 -21
- package/dist/eval-trajectory-store.js +2 -2
- package/dist/eval-trajectory.js +2 -2
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.d.ts +3 -2
- package/dist/explicit-capture.d.ts.map +1 -1
- package/dist/explicit-capture.js +530 -6
- package/dist/explicit-cue-recall.js +2 -2
- package/dist/external-wiki-access.js +2 -283
- package/dist/extraction-faithfulness.js +4 -4
- package/dist/extraction-judge.js +5 -5
- package/dist/extraction-normalization.js +1 -1
- package/dist/extraction-quote-grounding.d.ts.map +1 -1
- package/dist/extraction-quote-grounding.js +1 -1
- package/dist/extraction-source-grounding-rules.js +2 -2
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.js +43 -44
- package/dist/fallback-llm-codex-subscription.js +2 -2
- package/dist/fallback-llm.js +3 -3
- package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
- package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
- package/dist/focused-list-recall.js +2 -2
- package/dist/harmonic-retrieval.js +20 -20
- package/dist/index.js +776 -797
- package/dist/lcm/index.js +2 -2
- package/dist/live-connectors-runner.d.ts +2 -0
- package/dist/live-connectors-runner.d.ts.map +1 -1
- package/dist/live-connectors-runner.js +3 -4
- package/dist/local-llm-config.js +2 -2
- package/dist/local-llm-helpers.d.ts +4 -0
- package/dist/local-llm-helpers.d.ts.map +1 -1
- package/dist/local-llm-helpers.js +3 -1
- package/dist/local-llm.d.ts +4 -1
- package/dist/local-llm.d.ts.map +1 -1
- package/dist/local-llm.js +4 -4
- package/dist/location/index.js +27 -29
- package/dist/maintenance/memory-governance.js +20 -20
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -20
- package/dist/maintenance/rebuild-memory-projection-runner.js +22 -22
- package/dist/maintenance/rebuild-memory-projection.js +21 -21
- package/dist/memory-browse-operations.js +2 -283
- package/dist/memory-browse.js +8 -7
- package/dist/memory-controller.js +2 -2
- package/dist/memory-subject.js +2 -2
- package/dist/namespaces/migrate.js +21 -21
- package/dist/namespaces/search.js +5 -5
- package/dist/namespaces/storage.js +20 -20
- package/dist/offline-sync-changeset-generations.js +1 -1
- package/dist/offline-sync-embedding-generation.js +1 -1
- package/dist/offline-sync-impression-drain.js +6 -6
- package/dist/offline-sync-snapshot-stream.js +1 -1
- package/dist/offline-sync.js +1 -1
- package/dist/operator-doctor-corpus.js +24 -24
- package/dist/operator-toolkit.js +58 -61
- package/dist/orchestration/backlog-scheduler.js +279 -295
- package/dist/orchestration/backlog-scheduler.js.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +279 -295
- package/dist/orchestration/extraction-run.d.ts +4 -0
- package/dist/orchestration/extraction-run.d.ts.map +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +39 -42
- package/dist/orchestrator.d.ts +11 -11
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +291 -308
- package/dist/page-versioning.js +3 -3
- package/dist/qmd-daemon-session.js +2 -2
- package/dist/qmd.js +3 -3
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.js +2 -2
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-navigation-operations.js +2 -283
- package/dist/recall-navigation.js +334 -61
- package/dist/recall-planner-llm.js +3 -3
- package/dist/recall-state-view-admission.js +6 -6
- package/dist/recall-why-service.js +10 -9
- package/dist/recall-why.js +4 -3
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/manifest.js +3 -3
- package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
- package/dist/response-guidance-recall.js +2 -2
- package/dist/resume-bundles.js +40 -41
- package/dist/salvage-envelope.js +4 -4
- package/dist/search/factory.js +4 -4
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +23 -23
- package/dist/semantic-rule-promotion.js +20 -20
- package/dist/semantic-rule-verifier.js +20 -20
- package/dist/shared-context/manager.js +2 -3
- package/dist/source-agent-qualifier.js +32 -33
- package/dist/standup/build.js +20 -20
- package/dist/{state-store-I5HLZTZT.js → state-store-EEAX2MBS.js} +2 -2
- package/dist/storage/content-hash-index.d.ts +10 -0
- package/dist/storage/content-hash-index.d.ts.map +1 -1
- package/dist/storage.js +19 -19
- package/dist/subject-config.js +3 -3
- package/dist/summarizer.d.ts +4 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +6 -6
- package/dist/support-passport/index.js +53 -297
- package/dist/targeted-fact-recall.js +2 -2
- package/dist/tier-routing.js +3 -2
- package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
- package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
- package/dist/transcript.js +2 -2
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +20 -20
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +20 -20
- package/dist/wearables/memory-gen.d.ts +5 -1
- package/dist/wearables/memory-gen.d.ts.map +1 -1
- package/dist/wearables/service.d.ts.map +1 -1
- package/dist/who-knows-cli.js +21 -21
- package/dist/who-knows.js +20 -20
- package/dist/write-envelope.js +3 -3
- package/package.json +2 -2
- package/src/access-boundary.ts +5 -1
- package/src/access-category-alias.test.ts +5 -0
- package/src/access-mcp-frames.test.ts +60 -5
- package/src/access-mcp-frames.ts +72 -18
- package/src/access-mcp.test.ts +66 -2
- package/src/access-mcp.ts +19 -13
- package/src/access-service-coding-write.test.ts +7 -0
- package/src/access-service.ts +3 -0
- package/src/activity/journal-extract-storage.test.ts +31 -0
- package/src/activity/journal-extract.test.ts +1 -0
- package/src/activity/journal-extract.ts +24 -3
- package/src/cli.ts +1 -2
- package/src/config.test.ts +17 -0
- package/src/config.ts +25 -7
- package/src/connectors/live/framework.ts +8 -0
- package/src/connectors/live/google-drive.test.ts +74 -0
- package/src/connectors/live/google-drive.ts +138 -11
- package/src/explicit-capture.test.ts +109 -17
- package/src/explicit-capture.ts +90 -37
- package/src/extraction-quote-grounding.test.ts +20 -0
- package/src/extraction-quote-grounding.ts +21 -11
- package/src/fact-dedup-window.test.ts +37 -0
- package/src/live-connectors-runner.ts +3 -0
- package/src/live-connectors-secretref.test.ts +126 -0
- package/src/local-llm-helpers.ts +12 -0
- package/src/local-llm-shutdown.test.ts +194 -0
- package/src/local-llm.ts +32 -30
- package/src/orchestration/extraction-run.ts +12 -3
- package/src/orchestration/fact-dedup-window.ts +5 -1
- package/src/orchestrator-connector-dedup.test.ts +9 -5
- package/src/orchestrator.ts +52 -55
- package/src/storage/content-hash-index.ts +27 -2
- package/src/storage-fact-hash-publish-failure.test.ts +87 -0
- package/src/summarizer-shutdown.test.ts +195 -0
- package/src/summarizer.ts +15 -2
- package/src/types.ts +10 -10
- package/src/wearables/memory-gen.test.ts +20 -0
- package/src/wearables/memory-gen.ts +14 -7
- package/src/wearables/service.ts +14 -1
- package/src/wearables/storage-io.test.ts +25 -0
- package/dist/chunk-27XYV2XL.js +0 -43
- package/dist/chunk-27XYV2XL.js.map +0 -1
- package/dist/chunk-5KBIOI4G.js.map +0 -1
- package/dist/chunk-66L5ZRJC.js.map +0 -1
- package/dist/chunk-7HTUYFR3.js.map +0 -1
- package/dist/chunk-AOFEMUHG.js.map +0 -1
- package/dist/chunk-AP6NCP3V.js.map +0 -1
- package/dist/chunk-APYY5CX3.js +0 -335
- package/dist/chunk-APYY5CX3.js.map +0 -1
- package/dist/chunk-ATPWMT2E.js +0 -223
- package/dist/chunk-ATPWMT2E.js.map +0 -1
- package/dist/chunk-BCCG7VWM.js.map +0 -1
- package/dist/chunk-E6M225BJ.js.map +0 -1
- package/dist/chunk-EQ4U73UH.js.map +0 -1
- package/dist/chunk-ETSJD5IV.js +0 -361
- package/dist/chunk-ETSJD5IV.js.map +0 -1
- package/dist/chunk-EWJZZDRG.js.map +0 -1
- package/dist/chunk-FHGWKODN.js +0 -2300
- package/dist/chunk-FHGWKODN.js.map +0 -1
- package/dist/chunk-GRTKLWYX.js +0 -621
- package/dist/chunk-GRTKLWYX.js.map +0 -1
- package/dist/chunk-IGLJNHOX.js +0 -62
- package/dist/chunk-IGLJNHOX.js.map +0 -1
- package/dist/chunk-IPT2OY4B.js.map +0 -1
- package/dist/chunk-IUKYFTGW.js.map +0 -1
- package/dist/chunk-KUIRDD74.js +0 -3991
- package/dist/chunk-KUIRDD74.js.map +0 -1
- package/dist/chunk-MVMRUGBH.js +0 -1919
- package/dist/chunk-MVMRUGBH.js.map +0 -1
- package/dist/chunk-ODL4ZTNI.js +0 -3644
- package/dist/chunk-ODL4ZTNI.js.map +0 -1
- package/dist/chunk-OGXXGHW6.js +0 -223
- package/dist/chunk-OGXXGHW6.js.map +0 -1
- package/dist/chunk-P5VZSSYL.js.map +0 -1
- package/dist/chunk-PPCLZ4JU.js +0 -28
- package/dist/chunk-PPCLZ4JU.js.map +0 -1
- package/dist/chunk-PUEO37QG.js.map +0 -1
- package/dist/chunk-RRTVGD3J.js +0 -792
- package/dist/chunk-RRTVGD3J.js.map +0 -1
- package/dist/chunk-STSLB4HT.js +0 -18793
- package/dist/chunk-STSLB4HT.js.map +0 -1
- package/dist/chunk-T6OZW2WR.js.map +0 -1
- package/dist/chunk-TRBGGJKZ.js +0 -32218
- package/dist/chunk-TRBGGJKZ.js.map +0 -1
- package/dist/chunk-UDKWFDV2.js.map +0 -1
- package/dist/chunk-V4XE263I.js.map +0 -1
- package/dist/chunk-V7IZKJI3.js +0 -22
- package/dist/chunk-V7IZKJI3.js.map +0 -1
- package/dist/chunk-VMVVSZTD.js +0 -61
- package/dist/chunk-VMVVSZTD.js.map +0 -1
- package/dist/chunk-VNUOAXWP.js +0 -203
- package/dist/chunk-VNUOAXWP.js.map +0 -1
- package/dist/chunk-WLBEZZK5.js +0 -729
- package/dist/chunk-WLBEZZK5.js.map +0 -1
- package/dist/chunk-WUUES6CU.js +0 -25
- package/dist/chunk-WUUES6CU.js.map +0 -1
- package/dist/chunk-WWK6J6SE.js.map +0 -1
- package/dist/chunk-XICOH5HT.js +0 -346
- package/dist/chunk-XICOH5HT.js.map +0 -1
- package/dist/chunk-YCNIGRRF.js.map +0 -1
- package/dist/chunk-YS2GHSWN.js.map +0 -1
- package/dist/chunk-ZCLEYJPB.js +0 -10143
- package/dist/chunk-ZCLEYJPB.js.map +0 -1
- /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
- /package/dist/{chunk-QL7RHYK6.js.map → chunk-2LMA22UU.js.map} +0 -0
- /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
- /package/dist/{chunk-RSK2WIEM.js.map → chunk-2YODOG2O.js.map} +0 -0
- /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
- /package/dist/{chunk-OFVCFKDH.js.map → chunk-3CYOCE6U.js.map} +0 -0
- /package/dist/{chunk-6HM6HARE.js.map → chunk-3FBAEPCU.js.map} +0 -0
- /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
- /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
- /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
- /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
- /package/dist/{chunk-3UMDRGFS.js.map → chunk-65HWAQ4F.js.map} +0 -0
- /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
- /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
- /package/dist/{chunk-63O7LVMN.js.map → chunk-B33YWUJV.js.map} +0 -0
- /package/dist/{chunk-QVMOCJRV.js.map → chunk-B377OKUP.js.map} +0 -0
- /package/dist/{chunk-ZT6WPSII.js.map → chunk-BGHCSLE5.js.map} +0 -0
- /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
- /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
- /package/dist/{chunk-EV76IZ37.js.map → chunk-BTI4C4MA.js.map} +0 -0
- /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
- /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
- /package/dist/{chunk-3XIWW4TB.js.map → chunk-CGJYL633.js.map} +0 -0
- /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
- /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
- /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
- /package/dist/{chunk-RO7RUBLD.js.map → chunk-EFL24KOI.js.map} +0 -0
- /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
- /package/dist/{chunk-Z4Q6USKJ.js.map → chunk-ETBVCRVL.js.map} +0 -0
- /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
- /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
- /package/dist/{chunk-SHUBTAY2.js.map → chunk-FMKILENU.js.map} +0 -0
- /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
- /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
- /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
- /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
- /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
- /package/dist/{chunk-JC36FPQ5.js.map → chunk-LPV72KRQ.js.map} +0 -0
- /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
- /package/dist/{chunk-VPQFK63H.js.map → chunk-MH632KPO.js.map} +0 -0
- /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
- /package/dist/{chunk-ZZ67UTQX.js.map → chunk-MUX7IL77.js.map} +0 -0
- /package/dist/{chunk-RYWROSL5.js.map → chunk-NAQ4A5IX.js.map} +0 -0
- /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
- /package/dist/{chunk-ADYP7HXE.js.map → chunk-NHVAX7MH.js.map} +0 -0
- /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
- /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
- /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
- /package/dist/{chunk-7L3TODI7.js.map → chunk-OJXJWNFY.js.map} +0 -0
- /package/dist/{chunk-EURPSF4Y.js.map → chunk-PH5ZQM35.js.map} +0 -0
- /package/dist/{chunk-BMWMTAQF.js.map → chunk-PRAUN6PN.js.map} +0 -0
- /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
- /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
- /package/dist/{chunk-AH637HZ5.js.map → chunk-QDESY76F.js.map} +0 -0
- /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
- /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
- /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
- /package/dist/{chunk-LJQFJF4J.js.map → chunk-RM2YUFH6.js.map} +0 -0
- /package/dist/{chunk-6QMWECO5.js.map → chunk-SGQPR5SX.js.map} +0 -0
- /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
- /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
- /package/dist/{chunk-5UCMYPVF.js.map → chunk-SVT5IA2H.js.map} +0 -0
- /package/dist/{chunk-FRSHF6FU.js.map → chunk-TJ4F6H4H.js.map} +0 -0
- /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
- /package/dist/{chunk-NJJMVS5Y.js.map → chunk-TRQE3RYR.js.map} +0 -0
- /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
- /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
- /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
- /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
- /package/dist/{chunk-KZSGBWOJ.js.map → chunk-V2KGDESV.js.map} +0 -0
- /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
- /package/dist/{chunk-N74SGESS.js.map → chunk-WKSQTMU7.js.map} +0 -0
- /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
- /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
- /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
- /package/dist/{chunk-GNXGJBCA.js.map → chunk-XIYLWEXC.js.map} +0 -0
- /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
- /package/dist/{chunk-7O2MC42R.js.map → chunk-XNRLRNQF.js.map} +0 -0
- /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
- /package/dist/{chunk-JBCCWVU2.js.map → chunk-XRDYRWZI.js.map} +0 -0
- /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
- /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
- /package/dist/{chunk-KEPXC4KN.js.map → chunk-ZSE774RV.js.map} +0 -0
- /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
- /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
- /package/dist/{state-store-I5HLZTZT.js.map → state-store-EEAX2MBS.js.map} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// rebuild saves plus the deferred durable-retry machinery. storage.ts imports
|
|
4
4
|
// these back and re-exports the class + companion types that were previously
|
|
5
5
|
// part of its public API.
|
|
6
|
-
import { mkdir, stat } from "node:fs/promises";
|
|
6
|
+
import { mkdir, stat, utimes } from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { computeContentHash, normalizeContent } from "../content-hash.js";
|
|
9
9
|
import { log } from "../logger.js";
|
|
@@ -291,6 +291,25 @@ export class ContentHashIndex {
|
|
|
291
291
|
);
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Change the durable file's mtime without touching its content. A publish
|
|
296
|
+
* that throws or stays deferred leaves the file as it was, so a peer that
|
|
297
|
+
* already synced it keeps answering authoritative MISSes for deltas this
|
|
298
|
+
* instance holds only in memory (#3355). A new stamp makes every holder of the
|
|
299
|
+
* old fingerprint, this instance included, fail the freshness check and
|
|
300
|
+
* confirm against the corpus. Best effort: a file that cannot be stamped
|
|
301
|
+
* leaves the callers' existing retry and rebuild paths as the only repair.
|
|
302
|
+
*/
|
|
303
|
+
private async invalidatePeerFreshness(): Promise<void> {
|
|
304
|
+
try {
|
|
305
|
+
const { mtimeMs } = await stat(this.filePath);
|
|
306
|
+
const bumped = new Date(Math.max(Date.now(), mtimeMs + 1));
|
|
307
|
+
await utimes(this.filePath, bumped, bumped);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
log.debug(`content-hash index: could not invalidate peer freshness of ${this.filePath}: ${err}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
294
313
|
/** Check if content already exists in the index. */
|
|
295
314
|
has(content: string): boolean {
|
|
296
315
|
return this.hashes.has(ContentHashIndex.computeHash(content));
|
|
@@ -473,9 +492,15 @@ export class ContentHashIndex {
|
|
|
473
492
|
`content-hash index: lock not acquired for ${this.filePath}; deferring reconcile save (dirty retained, scheduling durable retry)`
|
|
474
493
|
);
|
|
475
494
|
this.scheduleReconcileRetry();
|
|
495
|
+
await this.invalidatePeerFreshness();
|
|
476
496
|
return;
|
|
477
497
|
}
|
|
478
|
-
|
|
498
|
+
try {
|
|
499
|
+
await publish();
|
|
500
|
+
} catch (err) {
|
|
501
|
+
await this.invalidatePeerFreshness();
|
|
502
|
+
throw err;
|
|
503
|
+
}
|
|
479
504
|
if (this.dirty) {
|
|
480
505
|
// A concurrent add()/remove() landed during publish's disk awaits, so
|
|
481
506
|
// deltas remain unpersisted (PR #2016 thread SD7Tj). Arm a bounded
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { StorageManager } from "./storage.js";
|
|
7
|
+
import { ContentHashIndex } from "./storage/content-hash-index.js";
|
|
8
|
+
|
|
9
|
+
const FACT = "the quarterly budget was approved by the board";
|
|
10
|
+
const CONTENDED_LOCK_OPTIONS = { maxWaitMs: 40, pollMs: 10, retryMaxAttempts: 2, retryBaseMs: 10 };
|
|
11
|
+
|
|
12
|
+
async function withMemoryDir(run: (dir: string) => Promise<void>): Promise<void> {
|
|
13
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-fact-hash-publish-"));
|
|
14
|
+
try {
|
|
15
|
+
await run(dir);
|
|
16
|
+
} finally {
|
|
17
|
+
await rm(dir, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Hold the fact-hash index lock the way a peer mid-publish does; returns the release. */
|
|
22
|
+
async function holdIndexLock(dir: string): Promise<() => Promise<void>> {
|
|
23
|
+
const lockPath = path.join(dir, "state", "fact-hashes.txt.lock");
|
|
24
|
+
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
25
|
+
await writeFile(lockPath, `99999 peer-owner ${new Date().toISOString()}\n`);
|
|
26
|
+
return () => rm(lockPath, { force: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("a publish that throws leaves peers revalidating instead of trusting the old file (#3355)", async () => {
|
|
30
|
+
await withMemoryDir(async (dir) => {
|
|
31
|
+
const stateDir = path.join(dir, "state");
|
|
32
|
+
const peer = new ContentHashIndex(stateDir);
|
|
33
|
+
peer.add("seed fact");
|
|
34
|
+
await peer.saveMergingWithDisk();
|
|
35
|
+
assert.equal(await peer.isDiskFingerprintCurrent(), true, "the peer starts in sync with the file");
|
|
36
|
+
|
|
37
|
+
const writer = new ContentHashIndex(
|
|
38
|
+
stateDir,
|
|
39
|
+
() => null,
|
|
40
|
+
() => {
|
|
41
|
+
throw new Error("write key unavailable");
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
await writer.load();
|
|
45
|
+
writer.add("pending fact");
|
|
46
|
+
await assert.rejects(writer.saveMergingWithDisk(), /write key unavailable/);
|
|
47
|
+
|
|
48
|
+
assert.equal(writer.hasPendingChanges, true, "the failed addition stays pending in the writer");
|
|
49
|
+
assert.equal(
|
|
50
|
+
await peer.isDiskFingerprintCurrent(),
|
|
51
|
+
false,
|
|
52
|
+
"the file did not change, yet the peer must stop trusting its snapshot",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("a writeMemory whose publish stays deferred does not leave a peer answering an authoritative MISS (#3355)", async () => {
|
|
58
|
+
await withMemoryDir(async (dir) => {
|
|
59
|
+
const peer = new StorageManager(dir);
|
|
60
|
+
const writer = new StorageManager(dir);
|
|
61
|
+
peer.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
|
|
62
|
+
writer.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
|
|
63
|
+
assert.deepEqual(
|
|
64
|
+
await peer.lookupContentHash(FACT),
|
|
65
|
+
{ present: false, authoritative: true },
|
|
66
|
+
"the peer starts with an authoritative, empty index",
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const release = await holdIndexLock(dir);
|
|
70
|
+
const written = await writer.writeMemory("fact", FACT);
|
|
71
|
+
assert.ok(written.id, "the memory is durable, so the write still succeeds");
|
|
72
|
+
|
|
73
|
+
const duringContention = await peer.lookupContentHash(FACT);
|
|
74
|
+
assert.ok(
|
|
75
|
+
duringContention.present || !duringContention.authoritative,
|
|
76
|
+
`a peer must not report an authoritative MISS for a fact on disk: ${JSON.stringify(duringContention)}`,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
await release();
|
|
80
|
+
assert.deepEqual(
|
|
81
|
+
await peer.lookupContentHash(FACT),
|
|
82
|
+
{ present: true, authoritative: true },
|
|
83
|
+
"once the lock is free the peer rebuilds from the corpus and sees the fact",
|
|
84
|
+
);
|
|
85
|
+
await writer.lookupContentHash(FACT);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Shutdown-signal behavioral tests for HourlySummarizer: an aborted
|
|
2
|
+
// summarizer must abort its in-flight local LLM call (the abort reaches the
|
|
3
|
+
// client stub) and must refuse to start new summary work once aborted.
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
|
|
10
|
+
import { parseConfig } from "./config.js";
|
|
11
|
+
import { initLogger, resetLogger } from "./logger.js";
|
|
12
|
+
import { HourlySummarizer } from "./summarizer.js";
|
|
13
|
+
import type { LocalLlmChatCompletionOptions } from "./local-llm-helpers.js";
|
|
14
|
+
import type { TranscriptEntry } from "./types.js";
|
|
15
|
+
|
|
16
|
+
function createConfig(memoryDir: string, extended = false) {
|
|
17
|
+
return parseConfig({
|
|
18
|
+
openaiApiKey: "sk-test",
|
|
19
|
+
memoryDir,
|
|
20
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
21
|
+
localLlmEnabled: true,
|
|
22
|
+
localLlmModel: "test-local-model",
|
|
23
|
+
localLlmUrl: "http://127.0.0.1:9",
|
|
24
|
+
localLlmTimeoutMs: 600_000,
|
|
25
|
+
localLlmRetry5xxCount: 1,
|
|
26
|
+
localLlmRetryBackoffMs: 30_000,
|
|
27
|
+
hourlySummariesEnabled: true,
|
|
28
|
+
hourlySummariesExtendedEnabled: extended,
|
|
29
|
+
debug: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface LlmStub {
|
|
34
|
+
calls: { signal: AbortSignal | undefined }[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function stubLocalLlm(summarizer: HourlySummarizer, stub: LlmStub): void {
|
|
38
|
+
// SAFETY: localLlm is a private field, stubbed at the established field
|
|
39
|
+
// seam with the client's real call shape; the stub parks until its caller
|
|
40
|
+
// signal aborts (real transport behavior), mirroring a wedged local LLM.
|
|
41
|
+
(summarizer as any).localLlm = {
|
|
42
|
+
chatCompletion: async (
|
|
43
|
+
messages: Array<{ role: string; content: string }>,
|
|
44
|
+
options: LocalLlmChatCompletionOptions,
|
|
45
|
+
): Promise<null> => {
|
|
46
|
+
void messages;
|
|
47
|
+
stub.calls.push({ signal: options.signal });
|
|
48
|
+
const { promise, reject } = Promise.withResolvers<null>();
|
|
49
|
+
const onAbort = (): void => {
|
|
50
|
+
reject(new DOMException("This operation was aborted", "AbortError"));
|
|
51
|
+
};
|
|
52
|
+
const signal = options.signal;
|
|
53
|
+
if (signal) {
|
|
54
|
+
if (signal.aborted) onAbort();
|
|
55
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
56
|
+
}
|
|
57
|
+
return await promise;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function withTestDeadline<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
|
63
|
+
const { promise: deadline, reject } = Promise.withResolvers<never>();
|
|
64
|
+
const timer = setTimeout(() => reject(new Error(`${label}: test deadline exceeded (${timeoutMs}ms)`)), timeoutMs);
|
|
65
|
+
timer.unref?.();
|
|
66
|
+
try {
|
|
67
|
+
return await Promise.race([promise, deadline]);
|
|
68
|
+
} finally {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function quietLogger(): void {
|
|
74
|
+
initLogger({ info() {}, warn() {}, error() {}, debug() {} }, false);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function entriesFor(): TranscriptEntry[] {
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
role: "user",
|
|
81
|
+
content: "bounded shutdown summarizer test entry",
|
|
82
|
+
timestamp: new Date(Date.now() - 3_600_000).toISOString(),
|
|
83
|
+
sessionKey: "sess-summary-shutdown",
|
|
84
|
+
turnId: "turn-shutdown-1",
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
test("aborting the shutdown signal aborts an in-flight generateSummary and reaches the LLM stub", async () => {
|
|
90
|
+
quietLogger();
|
|
91
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-inflight-"));
|
|
92
|
+
const stub: LlmStub = { calls: [] };
|
|
93
|
+
try {
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const summarizer = new HourlySummarizer(
|
|
96
|
+
createConfig(memoryDir),
|
|
97
|
+
undefined,
|
|
98
|
+
undefined,
|
|
99
|
+
undefined,
|
|
100
|
+
controller.signal,
|
|
101
|
+
);
|
|
102
|
+
stubLocalLlm(summarizer, stub);
|
|
103
|
+
|
|
104
|
+
const hourStart = new Date(Date.now() - 3_600_000);
|
|
105
|
+
hourStart.setMinutes(0, 0, 0);
|
|
106
|
+
const pending = summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor());
|
|
107
|
+
|
|
108
|
+
await withTestDeadline((async () => {
|
|
109
|
+
while (stub.calls.length === 0) {
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
111
|
+
}
|
|
112
|
+
})(), 5_000, "LLM stub to be entered");
|
|
113
|
+
controller.abort();
|
|
114
|
+
|
|
115
|
+
const result = await withTestDeadline(pending, 2_000, "in-flight summarizer abort");
|
|
116
|
+
assert.equal(result, null);
|
|
117
|
+
assert.equal(stub.calls.length, 1, "no second LLM attempt may start after abort");
|
|
118
|
+
assert.equal(stub.calls[0]?.signal?.aborted, true, "the shutdown abort must reach the LLM stub");
|
|
119
|
+
} finally {
|
|
120
|
+
resetLogger();
|
|
121
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("an already-aborted summarizer refuses to start new work and never contacts the LLM", async () => {
|
|
126
|
+
quietLogger();
|
|
127
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-refuse-"));
|
|
128
|
+
const stub: LlmStub = { calls: [] };
|
|
129
|
+
try {
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
controller.abort();
|
|
132
|
+
const summarizer = new HourlySummarizer(
|
|
133
|
+
createConfig(memoryDir),
|
|
134
|
+
undefined,
|
|
135
|
+
undefined,
|
|
136
|
+
undefined,
|
|
137
|
+
controller.signal,
|
|
138
|
+
);
|
|
139
|
+
stubLocalLlm(summarizer, stub);
|
|
140
|
+
|
|
141
|
+
const hourStart = new Date(Date.now() - 3_600_000);
|
|
142
|
+
hourStart.setMinutes(0, 0, 0);
|
|
143
|
+
await assert.rejects(
|
|
144
|
+
summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor()),
|
|
145
|
+
);
|
|
146
|
+
assert.equal(stub.calls.length, 0, "aborted summarizer must not start new LLM work");
|
|
147
|
+
} finally {
|
|
148
|
+
resetLogger();
|
|
149
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
for (const extended of [false, true]) {
|
|
154
|
+
test(`a shutdown abort during the local call does not start a gateway call (${extended ? "extended" : "standard"} summary)`, async () => {
|
|
155
|
+
quietLogger();
|
|
156
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-gateway-"));
|
|
157
|
+
const stub: LlmStub = { calls: [] };
|
|
158
|
+
let gatewayCalls = 0;
|
|
159
|
+
try {
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
const summarizer = new HourlySummarizer(
|
|
162
|
+
createConfig(memoryDir, extended),
|
|
163
|
+
undefined,
|
|
164
|
+
undefined,
|
|
165
|
+
undefined,
|
|
166
|
+
controller.signal,
|
|
167
|
+
);
|
|
168
|
+
stubLocalLlm(summarizer, stub);
|
|
169
|
+
// SAFETY: fallbackLlm is a private field, stubbed at the same field seam
|
|
170
|
+
// as localLlm; the stub counts gateway calls and returns no result.
|
|
171
|
+
(summarizer as any).fallbackLlm = {
|
|
172
|
+
parseWithSchema: async (): Promise<null> => {
|
|
173
|
+
gatewayCalls += 1;
|
|
174
|
+
return null;
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const hourStart = new Date(Date.now() - 3_600_000);
|
|
179
|
+
hourStart.setMinutes(0, 0, 0);
|
|
180
|
+
const pending = summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor());
|
|
181
|
+
await withTestDeadline((async () => {
|
|
182
|
+
while (stub.calls.length === 0) {
|
|
183
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
184
|
+
}
|
|
185
|
+
})(), 5_000, "LLM stub to be entered");
|
|
186
|
+
controller.abort();
|
|
187
|
+
|
|
188
|
+
assert.equal(await withTestDeadline(pending, 2_000, "in-flight summarizer abort"), null);
|
|
189
|
+
assert.equal(gatewayCalls, 0, "no gateway call may start after the shutdown abort");
|
|
190
|
+
} finally {
|
|
191
|
+
resetLogger();
|
|
192
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
package/src/summarizer.ts
CHANGED
|
@@ -114,20 +114,25 @@ export class HourlySummarizer {
|
|
|
114
114
|
private fallbackLlm: FallbackLlmClient;
|
|
115
115
|
private modelRegistry: ModelRegistry;
|
|
116
116
|
private transcript?: TranscriptManager;
|
|
117
|
+
/** Orchestrator-owned stop signal: refuses new summaries and aborts the
|
|
118
|
+
* in-flight local LLM call once teardown begins. */
|
|
119
|
+
private readonly shutdownSignal: AbortSignal | undefined;
|
|
117
120
|
|
|
118
121
|
constructor(
|
|
119
122
|
config: PluginConfig,
|
|
120
123
|
gatewayConfig?: GatewayConfig,
|
|
121
124
|
modelRegistry?: ModelRegistry,
|
|
122
|
-
transcript?: TranscriptManager
|
|
125
|
+
transcript?: TranscriptManager,
|
|
126
|
+
shutdownSignal?: AbortSignal
|
|
123
127
|
) {
|
|
124
128
|
this.config = config;
|
|
125
129
|
this.summariesDir = path.join(config.memoryDir, "summaries", "hourly");
|
|
126
130
|
this.modelRegistry = modelRegistry ?? new ModelRegistry(config.memoryDir);
|
|
127
131
|
this.transcript = transcript;
|
|
132
|
+
this.shutdownSignal = shutdownSignal;
|
|
128
133
|
|
|
129
134
|
// Initialize local LLM client with shared model registry
|
|
130
|
-
this.localLlm = new LocalLlmClient(config, this.modelRegistry);
|
|
135
|
+
this.localLlm = new LocalLlmClient(config, this.modelRegistry, shutdownSignal);
|
|
131
136
|
|
|
132
137
|
// Initialize fallback client with gateway config
|
|
133
138
|
this.fallbackLlm = new FallbackLlmClient(gatewayConfig, fallbackLlmRuntimeContextFromConfig(config));
|
|
@@ -190,6 +195,7 @@ export class HourlySummarizer {
|
|
|
190
195
|
entries: TranscriptEntry[]
|
|
191
196
|
): Promise<HourlySummary | null> {
|
|
192
197
|
if (entries.length === 0) return null;
|
|
198
|
+
this.shutdownSignal?.throwIfAborted();
|
|
193
199
|
|
|
194
200
|
// Format entries for the LLM
|
|
195
201
|
const conversation = entries.map((e) => `[${e.role}] ${e.content}`).join("\n\n");
|
|
@@ -275,6 +281,8 @@ export class HourlySummarizer {
|
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
|
|
284
|
+
if (this.shutdownSignal?.aborted) return null;
|
|
285
|
+
|
|
278
286
|
// Fall back to gateway's default AI
|
|
279
287
|
log.info("summary generation: falling back to gateway default AI");
|
|
280
288
|
|
|
@@ -399,6 +407,7 @@ Respond with valid JSON matching this schema:
|
|
|
399
407
|
maxTokens: contextSizes.maxOutputTokens,
|
|
400
408
|
operation: "hourly_summary_extended",
|
|
401
409
|
priority: "background",
|
|
410
|
+
signal: this.shutdownSignal,
|
|
402
411
|
}
|
|
403
412
|
);
|
|
404
413
|
if (response?.content) {
|
|
@@ -422,6 +431,8 @@ Respond with valid JSON matching this schema:
|
|
|
422
431
|
}
|
|
423
432
|
}
|
|
424
433
|
|
|
434
|
+
if (this.shutdownSignal?.aborted) return null;
|
|
435
|
+
|
|
425
436
|
try {
|
|
426
437
|
const result = await this.fallbackLlm.parseWithSchema(
|
|
427
438
|
[
|
|
@@ -489,6 +500,7 @@ Respond with valid JSON matching this schema:
|
|
|
489
500
|
maxTokens: contextSizes.maxOutputTokens,
|
|
490
501
|
operation: "hourly_summary",
|
|
491
502
|
priority: "background",
|
|
503
|
+
signal: this.shutdownSignal,
|
|
492
504
|
}
|
|
493
505
|
);
|
|
494
506
|
|
|
@@ -775,6 +787,7 @@ Respond with valid JSON matching this schema:
|
|
|
775
787
|
|
|
776
788
|
// Main entry point for cron job
|
|
777
789
|
async runHourly(): Promise<HourlySummarizerRunStats> {
|
|
790
|
+
this.shutdownSignal?.throwIfAborted();
|
|
778
791
|
log.debug("running hourly summary generation");
|
|
779
792
|
|
|
780
793
|
// Get active sessions from transcript. The scan outcome is kept LOCAL to
|
package/src/types.ts
CHANGED
|
@@ -273,8 +273,9 @@ export interface NativeKnowledgeOpenClawWorkspaceConfig {
|
|
|
273
273
|
* OpenClaw resolves these at runtime via its built-in secret resolver
|
|
274
274
|
* (e.g. exec providers like `kc_*` for macOS Keychain). Plugins receive
|
|
275
275
|
* the raw object in `pluginConfig` and must call the gateway's resolver
|
|
276
|
-
* before using the value.
|
|
277
|
-
*
|
|
276
|
+
* before using the value. A live connector credential field resolves a
|
|
277
|
+
* SecretRef at poll time through the host resolver. Other surfaces still
|
|
278
|
+
* need a plain string or `${ENV_VAR}` expansion.
|
|
278
279
|
*/
|
|
279
280
|
export interface SecretRef {
|
|
280
281
|
source: string;
|
|
@@ -2539,21 +2540,20 @@ export interface LiveConnectorsConfig {
|
|
|
2539
2540
|
* shape (frozen, post-validation). This interface is the pre-validation
|
|
2540
2541
|
* shape that `parseConfig` round-trips through.
|
|
2541
2542
|
*
|
|
2542
|
-
* `clientId` / `clientSecret` / `refreshToken` are
|
|
2543
|
-
*
|
|
2544
|
-
*
|
|
2545
|
-
*
|
|
2546
|
-
* privacy policy in CLAUDE.md applies.
|
|
2543
|
+
* `clientId` / `clientSecret` / `refreshToken` are a plain string or a
|
|
2544
|
+
* SecretRef. A SecretRef is kept verbatim here and resolved at poll time
|
|
2545
|
+
* through the host resolver. They MUST NEVER be committed to source. The
|
|
2546
|
+
* repo-wide privacy policy in CLAUDE.md applies.
|
|
2547
2547
|
*/
|
|
2548
2548
|
export interface GoogleDriveLiveConnectorConfig {
|
|
2549
2549
|
/** Master gate. Default false — operators must opt in explicitly. */
|
|
2550
2550
|
enabled: boolean;
|
|
2551
2551
|
/** OAuth2 client id. Populate from a secret store; never commit. */
|
|
2552
|
-
clientId: string;
|
|
2552
|
+
clientId: string | SecretRef;
|
|
2553
2553
|
/** OAuth2 client secret. Populate from a secret store; never commit. */
|
|
2554
|
-
clientSecret: string;
|
|
2554
|
+
clientSecret: string | SecretRef;
|
|
2555
2555
|
/** OAuth2 refresh token. Populate from a secret store; never commit. */
|
|
2556
|
-
refreshToken: string;
|
|
2556
|
+
refreshToken: string | SecretRef;
|
|
2557
2557
|
/** Poll interval in ms. Default 300000 (5 min); min 1000; max 86400000 (24h). */
|
|
2558
2558
|
pollIntervalMs: number;
|
|
2559
2559
|
/** Optional folder-id scope. Empty array = all accessible files. */
|
|
@@ -224,6 +224,26 @@ test("auto mode writes active status", async () => {
|
|
|
224
224
|
assert.equal(writes[0].options.status, "active");
|
|
225
225
|
});
|
|
226
226
|
|
|
227
|
+
test("a fact write the dedup window suppressed counts as skipped, not created", async () => {
|
|
228
|
+
const { writer } = makeWriter();
|
|
229
|
+
writer.writeSealedMemory = async () => ({ duplicate: true });
|
|
230
|
+
const result = await generateWearableMemories(
|
|
231
|
+
"limitless",
|
|
232
|
+
"2026-06-10",
|
|
233
|
+
[LONG_CONVERSATION],
|
|
234
|
+
settings({ memoryMode: "auto" }),
|
|
235
|
+
REGISTRY,
|
|
236
|
+
{
|
|
237
|
+
extract: extractionReturning([
|
|
238
|
+
{ category: "fact", content: "Vendor call happens tomorrow morning.", confidence: 0.8, tags: [] },
|
|
239
|
+
]),
|
|
240
|
+
writer,
|
|
241
|
+
},
|
|
242
|
+
);
|
|
243
|
+
assert.equal(result.created, 0);
|
|
244
|
+
assert.equal(result.skippedByReason["duplicate-existing"], 1);
|
|
245
|
+
});
|
|
246
|
+
|
|
227
247
|
test("auto mode still refuses to write a high-impact personal claim active (#2294)", async () => {
|
|
228
248
|
// `auto` has no trust scoring, and an operator may lower minConfidence to or
|
|
229
249
|
// below the speculative ceiling the ambient clamp writes — which would let a
|
|
@@ -92,6 +92,11 @@ export function memoryStatusForMode(
|
|
|
92
92
|
return mode === "auto" || mode === "smart" ? "active" : "pending_review";
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/** A fact write the fact-dedup window suppressed because a concurrent writer committed the same fact first. */
|
|
96
|
+
export interface WearableDuplicateWrite {
|
|
97
|
+
readonly duplicate: true;
|
|
98
|
+
}
|
|
99
|
+
|
|
95
100
|
/** Narrow writer interface satisfied by `StorageManager`. */
|
|
96
101
|
export interface WearableMemoryWriter {
|
|
97
102
|
/**
|
|
@@ -106,7 +111,7 @@ export interface WearableMemoryWriter {
|
|
|
106
111
|
status?: MemoryStatus;
|
|
107
112
|
memoryKind?: "episode" | "note" | "box" | "dream" | "procedural";
|
|
108
113
|
},
|
|
109
|
-
): Promise<MemoryWriteResult>;
|
|
114
|
+
): Promise<MemoryWriteResult | WearableDuplicateWrite>;
|
|
110
115
|
hasFactContentHash(content: string): Promise<boolean>;
|
|
111
116
|
/**
|
|
112
117
|
* Locate an earlier wearable write of the same content (any status).
|
|
@@ -663,7 +668,9 @@ export async function generateWearableMemories(
|
|
|
663
668
|
// #1645: a tombstone-blocked wearable write lands pending_review (no active
|
|
664
669
|
// copy) — count it as blocked, NOT created, so the sync summary and callers
|
|
665
670
|
// don't report blocked content as successfully created active memories.
|
|
666
|
-
if (writeResult
|
|
671
|
+
if ("duplicate" in writeResult) {
|
|
672
|
+
skip("duplicate-existing");
|
|
673
|
+
} else if (writeResult.tombstoneBlocked) {
|
|
667
674
|
result.tombstoneBlocked += 1;
|
|
668
675
|
} else {
|
|
669
676
|
result.created += 1;
|
|
@@ -872,11 +879,11 @@ export async function importNativeMemories(
|
|
|
872
879
|
// #1645: a tombstone-blocked native import lands pending_review (no active
|
|
873
880
|
// copy) — count it as blocked, NOT imported. Still record the provider id in
|
|
874
881
|
// importedIds so the next sync doesn't re-import and duplicate the pending
|
|
875
|
-
// row; the block is permanent for this content.
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
imported += 1;
|
|
882
|
+
// row; the block is permanent for this content. A write the dedup window
|
|
883
|
+
// suppressed (a concurrent writer committed the fact first) counts as neither.
|
|
884
|
+
if (!("duplicate" in nativeWriteResult)) {
|
|
885
|
+
if (nativeWriteResult.tombstoneBlocked) tombstoneBlocked += 1;
|
|
886
|
+
else imported += 1;
|
|
880
887
|
}
|
|
881
888
|
importedIds.push(memory.id);
|
|
882
889
|
}
|
package/src/wearables/service.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "./corrections.js";
|
|
14
14
|
import { describeErrorForOperator, WearablesInputError } from "./errors.js";
|
|
15
15
|
import { inferMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
|
|
16
|
+
import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
|
|
16
17
|
import type { LocationConfig } from "../location/types.js";
|
|
17
18
|
import { isSupportPassportPrivateMemory } from "../support-passport/card-projection.js";
|
|
18
19
|
import {
|
|
@@ -194,7 +195,19 @@ export function createWearableMemoryWriter(
|
|
|
194
195
|
storage: WearableStorageIo,
|
|
195
196
|
): WearableMemoryGenDeps["writer"] {
|
|
196
197
|
return {
|
|
197
|
-
writeSealedMemory:
|
|
198
|
+
writeSealedMemory: async (envelope, extras) => {
|
|
199
|
+
// Facts register in the fact hash index, so extraction and a wearable sync
|
|
200
|
+
// can commit the same new fact. Commit it under the window extraction uses
|
|
201
|
+
// and recheck the index there. Other categories keep the direct write: the
|
|
202
|
+
// index does not cover them, and the corpus scan stays outside the window.
|
|
203
|
+
if (envelope.category !== "fact") return storage.writeSealedMemory(envelope, extras);
|
|
204
|
+
const outcome = await persistUnderFactDedupWindow(
|
|
205
|
+
storage.dir,
|
|
206
|
+
() => storage.hasFactContentHash(extras.contentHashSource ?? envelope.content),
|
|
207
|
+
() => storage.writeSealedMemory(envelope, extras),
|
|
208
|
+
);
|
|
209
|
+
return outcome.suppressed ? { duplicate: true } : outcome.result;
|
|
210
|
+
},
|
|
198
211
|
findWearableMemoryByContent: async (content: string) =>
|
|
199
212
|
(await storage.findWearableMemoryByContent(content)) as
|
|
200
213
|
| { id: string; status: import("../types.js").MemoryStatus | undefined }
|
|
@@ -5,7 +5,10 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { test } from "node:test";
|
|
7
7
|
|
|
8
|
+
import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
|
|
8
9
|
import { StorageManager } from "../storage.js";
|
|
10
|
+
import { composeMemoryEnvelope } from "../write-envelope.js";
|
|
11
|
+
import { createWearableMemoryWriter } from "./service.js";
|
|
9
12
|
|
|
10
13
|
function makeStorage(): { storage: StorageManager; dir: string } {
|
|
11
14
|
const dir = mkdtempSync(path.join(tmpdir(), "remnic-wearable-io-"));
|
|
@@ -330,3 +333,25 @@ test("a symlinked _fusion dir that resolves INSIDE the memory dir is refused, no
|
|
|
330
333
|
rmSync(dir, { recursive: true, force: true });
|
|
331
334
|
}
|
|
332
335
|
});
|
|
336
|
+
|
|
337
|
+
test("a wearable fact write loses to a fact another writer committed first and writes nothing", async () => {
|
|
338
|
+
const { storage, dir } = makeStorage();
|
|
339
|
+
try {
|
|
340
|
+
await storage.ensureDirectories();
|
|
341
|
+
const fact = "Dana moved the review meeting to Thursday afternoon.";
|
|
342
|
+
const writer = createWearableMemoryWriter(storage);
|
|
343
|
+
const envelope = composeMemoryEnvelope(
|
|
344
|
+
{ content: fact, category: "fact", tags: ["wearable"] },
|
|
345
|
+
{ source: "wearable:limitless" },
|
|
346
|
+
);
|
|
347
|
+
await persistUnderFactDedupWindow(storage.dir, async () => false, () => storage.writeMemory("fact", fact));
|
|
348
|
+
|
|
349
|
+
const written = await writer.writeSealedMemory(envelope, { contentHashSource: fact, status: "active" });
|
|
350
|
+
|
|
351
|
+
assert.deepEqual(written, { duplicate: true });
|
|
352
|
+
const copies = (await storage.readAllMemories()).filter((memory) => memory.content.includes(fact));
|
|
353
|
+
assert.equal(copies.length, 1, "the corpus holds one copy of the fact");
|
|
354
|
+
} finally {
|
|
355
|
+
rmSync(dir, { recursive: true, force: true });
|
|
356
|
+
}
|
|
357
|
+
});
|
package/dist/chunk-27XYV2XL.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isNamespaceKind
|
|
3
|
-
} from "./chunk-PUEO37QG.js";
|
|
4
|
-
import {
|
|
5
|
-
EngramAccessInputError
|
|
6
|
-
} from "./chunk-CJCTKL6Y.js";
|
|
7
|
-
|
|
8
|
-
// src/access-http-query.ts
|
|
9
|
-
function optionalQueryString(value, label) {
|
|
10
|
-
if (value === void 0 || value === null || value === "") return void 0;
|
|
11
|
-
if (typeof value !== "string") {
|
|
12
|
-
throw new EngramAccessInputError(`${label} must be a string (got ${JSON.stringify(value)})`);
|
|
13
|
-
}
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
function nonEmptyQueryParam(value) {
|
|
17
|
-
return value !== null && value.length > 0 ? value : void 0;
|
|
18
|
-
}
|
|
19
|
-
function positiveIntQueryParam(value, label) {
|
|
20
|
-
if (value === null || value.length === 0) return void 0;
|
|
21
|
-
const parsed = Number(value);
|
|
22
|
-
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
23
|
-
throw new EngramAccessInputError(`${label} expects a positive integer`);
|
|
24
|
-
}
|
|
25
|
-
return parsed;
|
|
26
|
-
}
|
|
27
|
-
function optionalNamespaceKindQueryParam(value) {
|
|
28
|
-
if (value === null || value.length === 0) return void 0;
|
|
29
|
-
if (!isNamespaceKind(value)) {
|
|
30
|
-
throw new EngramAccessInputError(
|
|
31
|
-
"kind must be one of: default, shared, project, branch, team-project, explicit"
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export {
|
|
38
|
-
optionalQueryString,
|
|
39
|
-
nonEmptyQueryParam,
|
|
40
|
-
positiveIntQueryParam,
|
|
41
|
-
optionalNamespaceKindQueryParam
|
|
42
|
-
};
|
|
43
|
-
//# sourceMappingURL=chunk-27XYV2XL.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/access-http-query.ts"],"sourcesContent":["import { EngramAccessInputError } from \"./access-service.js\";\nimport { isNamespaceKind, type NamespaceKind } from \"./namespaces/catalog.js\";\n\n/** Optional string field from a JSON body: absent/null/\"\" → undefined. */\nexport function optionalQueryString(value: unknown, label: string): string | undefined {\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value !== \"string\") {\n throw new EngramAccessInputError(`${label} must be a string (got ${JSON.stringify(value)})`);\n }\n return value;\n}\n\n/** Optional non-empty query param: null/\"\" → undefined. */\nexport function nonEmptyQueryParam(value: string | null): string | undefined {\n return value !== null && value.length > 0 ? value : undefined;\n}\n\n/** Optional positive-integer query param; rejects invalid values. */\nexport function positiveIntQueryParam(value: string | null, label: string): number | undefined {\n if (value === null || value.length === 0) return undefined;\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {\n throw new EngramAccessInputError(`${label} expects a positive integer`);\n }\n return parsed;\n}\n\n/** Optional namespace-kind query param; rejects dead or unknown kinds. */\nexport function optionalNamespaceKindQueryParam(value: string | null): NamespaceKind | undefined {\n if (value === null || value.length === 0) return undefined;\n if (!isNamespaceKind(value)) {\n throw new EngramAccessInputError(\n \"kind must be one of: default, shared, project, branch, team-project, explicit\",\n );\n }\n return value;\n}\n"],"mappings":";;;;;;;;AAIO,SAAS,oBAAoB,OAAgB,OAAmC;AACrF,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,QAAO;AAClE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI,uBAAuB,GAAG,KAAK,0BAA0B,KAAK,UAAU,KAAK,CAAC,GAAG;AAAA,EAC7F;AACA,SAAO;AACT;AAGO,SAAS,mBAAmB,OAA0C;AAC3E,SAAO,UAAU,QAAQ,MAAM,SAAS,IAAI,QAAQ;AACtD;AAGO,SAAS,sBAAsB,OAAsB,OAAmC;AAC7F,MAAI,UAAU,QAAQ,MAAM,WAAW,EAAG,QAAO;AACjD,QAAM,SAAS,OAAO,KAAK;AAC3B,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,KAAK,CAAC,OAAO,UAAU,MAAM,GAAG;AACxE,UAAM,IAAI,uBAAuB,GAAG,KAAK,6BAA6B;AAAA,EACxE;AACA,SAAO;AACT;AAGO,SAAS,gCAAgC,OAAiD;AAC/F,MAAI,UAAU,QAAQ,MAAM,WAAW,EAAG,QAAO;AACjD,MAAI,CAAC,gBAAgB,KAAK,GAAG;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|