@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
package/src/access-mcp.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
import { EngramAccessInputError, type EngramAccessRecallResponse, type EngramAccessService } from "./access-service.js";
|
|
33
33
|
import { EngramAccessForbiddenError, unknownToolError } from "./access-errors.js";
|
|
34
34
|
import { log } from "./logger.js";
|
|
35
|
-
import { McpFrameReader } from "./access-mcp-frames.js";
|
|
35
|
+
import { McpFrameReader, type McpFrameForm } from "./access-mcp-frames.js";
|
|
36
36
|
import "./access-operations.js";
|
|
37
37
|
import { validateBriefingFormat } from "./briefing.js";
|
|
38
38
|
import { processChatMessage } from "./chat/chat-factory.js";
|
|
@@ -470,6 +470,8 @@ async function getMcpServerVersion(): Promise<string> {
|
|
|
470
470
|
|
|
471
471
|
export class EngramMcpServer {
|
|
472
472
|
private readonly frames = new McpFrameReader();
|
|
473
|
+
/** The form of the first frame on this connection. Replies use the same form. */
|
|
474
|
+
private replyForm: McpFrameForm | null = null;
|
|
473
475
|
private flushTask: Promise<void> | null = null;
|
|
474
476
|
private readonly tools: McpTool[];
|
|
475
477
|
private readonly resources: McpResource[];
|
|
@@ -2493,7 +2495,7 @@ export class EngramMcpServer {
|
|
|
2493
2495
|
private async flushBuffer(output: Writable): Promise<void> {
|
|
2494
2496
|
for (let frame = this.frames.next(); frame; frame = this.frames.next()) {
|
|
2495
2497
|
if (frame.kind === "too-large") {
|
|
2496
|
-
this.
|
|
2498
|
+
this.writeRefusal(output, frame.form, -32600, "frame too large");
|
|
2497
2499
|
continue;
|
|
2498
2500
|
}
|
|
2499
2501
|
let parsed: JsonRpcRequest;
|
|
@@ -2501,16 +2503,10 @@ export class EngramMcpServer {
|
|
|
2501
2503
|
// SAFETY: handleRequest defaults missing fields; a non-object body throws and scheduleFlush answers -32700.
|
|
2502
2504
|
parsed = JSON.parse(frame.text) as JsonRpcRequest;
|
|
2503
2505
|
} catch {
|
|
2504
|
-
this.
|
|
2505
|
-
jsonrpc: "2.0",
|
|
2506
|
-
id: null,
|
|
2507
|
-
error: {
|
|
2508
|
-
code: -32700,
|
|
2509
|
-
message: "Parse error",
|
|
2510
|
-
},
|
|
2511
|
-
});
|
|
2506
|
+
this.writeRefusal(output, frame.form, -32700, "Parse error");
|
|
2512
2507
|
continue;
|
|
2513
2508
|
}
|
|
2509
|
+
this.replyForm ??= frame.form;
|
|
2514
2510
|
const response = await this.handleRequest(parsed);
|
|
2515
2511
|
if (response) {
|
|
2516
2512
|
this.writeMessage(output, response);
|
|
@@ -2519,9 +2515,19 @@ export class EngramMcpServer {
|
|
|
2519
2515
|
}
|
|
2520
2516
|
|
|
2521
2517
|
private writeMessage(output: Writable, payload: Record<string, unknown>): void {
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2518
|
+
output.write(this.frameBody(JSON.stringify(payload), this.replyForm));
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
/** Answers a frame that held no valid JSON in its own form, without fixing the connection's reply form. */
|
|
2522
|
+
private writeRefusal(output: Writable, frameForm: McpFrameForm, code: number, message: string): void {
|
|
2523
|
+
const body = JSON.stringify({ jsonrpc: "2.0", id: null, error: { code, message } });
|
|
2524
|
+
output.write(this.frameBody(body, this.replyForm ?? frameForm));
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
private frameBody(body: string, form: McpFrameForm | null): string {
|
|
2528
|
+
return form === "line"
|
|
2529
|
+
? `${body}\n`
|
|
2530
|
+
: `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n${body}`;
|
|
2525
2531
|
}
|
|
2526
2532
|
|
|
2527
2533
|
private toolAcceptsArgument(name: string, key: string): boolean {
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* - Precedence: explicit `namespace` wins; namespaces-disabled is a no-op.
|
|
20
20
|
*/
|
|
21
21
|
import assert from "node:assert/strict";
|
|
22
|
+
import os from "node:os";
|
|
23
|
+
import path from "node:path";
|
|
22
24
|
import test from "node:test";
|
|
23
25
|
|
|
24
26
|
import { EngramAccessService } from "./access-service.js";
|
|
@@ -39,9 +41,14 @@ import { sealedWriteToLegacyArgs, type SealedMemoryEnvelope } from "./write-enve
|
|
|
39
41
|
// the PRODUCTION mapping (`sealedWriteToLegacyArgs`), so mock behavior
|
|
40
42
|
// cannot drift from the real envelope→options translation.
|
|
41
43
|
function withSealedWrite<T extends { writeMemory: (...args: never[]) => unknown }>(stub: T): T {
|
|
44
|
+
// SAFETY: the decorated stub only gains writeSealedMemory and dir, both assigned below before any caller reads them.
|
|
42
45
|
const decorated = stub as T & {
|
|
43
46
|
writeSealedMemory?: (envelope: SealedMemoryEnvelope, extras?: Record<string, unknown>) => unknown;
|
|
47
|
+
dir?: string;
|
|
44
48
|
};
|
|
49
|
+
// #3354: the fact-dedup window keys its lock on storage.dir and takes a
|
|
50
|
+
// real file lock, so the fake needs a writable per-process dir.
|
|
51
|
+
decorated.dir = path.join(os.tmpdir(), `remnic-coding-write-fake-${process.pid}`);
|
|
45
52
|
decorated.writeSealedMemory = (envelope, extras = {}) => {
|
|
46
53
|
const { category, content, options } = sealedWriteToLegacyArgs(envelope, extras);
|
|
47
54
|
return (stub.writeMemory as (c: unknown, b: unknown, o: unknown) => unknown)(category, content, options);
|
package/src/access-service.ts
CHANGED
|
@@ -1365,6 +1365,7 @@ export class EngramAccessService extends SupportPassportAccessServiceBase {
|
|
|
1365
1365
|
}
|
|
1366
1366
|
private readonly extractionLivenessWarn = new ExtractionLivenessWarnThrottle();
|
|
1367
1367
|
|
|
1368
|
+
private readonly secretRefResolver: ResolveSecretRefFn | null;
|
|
1368
1369
|
constructor(
|
|
1369
1370
|
private readonly orchestrator: Orchestrator,
|
|
1370
1371
|
options: {
|
|
@@ -1375,6 +1376,7 @@ export class EngramAccessService extends SupportPassportAccessServiceBase {
|
|
|
1375
1376
|
) {
|
|
1376
1377
|
super();
|
|
1377
1378
|
this.idempotency = new AccessIdempotencyStore(orchestrator.config.memoryDir);
|
|
1379
|
+
this.secretRefResolver = options.resolveSecretRef ?? null;
|
|
1378
1380
|
// Peer SecretRef tokens resolve at poll time through the host resolver, the
|
|
1379
1381
|
// same indirection as agentAccessHttp.authToken (review round 1). Absent a
|
|
1380
1382
|
// resolver, string/${ENV} tokens still work and a SecretRef degrades to a
|
|
@@ -3721,6 +3723,7 @@ export class EngramAccessService extends SupportPassportAccessServiceBase {
|
|
|
3721
3723
|
this.writableNamespaceFor(undefined, undefined, request.authenticatedPrincipal ?? principal);
|
|
3722
3724
|
return this.orchestrator.runLiveConnectors({
|
|
3723
3725
|
force: request.force === true,
|
|
3726
|
+
resolveSecretRef: this.secretRefResolver,
|
|
3724
3727
|
});
|
|
3725
3728
|
}
|
|
3726
3729
|
|
|
@@ -6,6 +6,7 @@ import test from "node:test";
|
|
|
6
6
|
import { parseConfig } from "../config.js";
|
|
7
7
|
import { NamespaceStorageRouter } from "../namespaces/storage.js";
|
|
8
8
|
import { StorageManager } from "../storage.js";
|
|
9
|
+
import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
|
|
9
10
|
import { withTempDir } from "../testing/tmp-dir.js";
|
|
10
11
|
import {
|
|
11
12
|
createJournalMemoryWriter,
|
|
@@ -127,3 +128,33 @@ test("tombstoned journal facts are skipped and not written as pending_review", a
|
|
|
127
128
|
assert.equal(pending.length, 0, "tombstoned content must not land as a new pending_review file");
|
|
128
129
|
});
|
|
129
130
|
});
|
|
131
|
+
|
|
132
|
+
test("a fact an extraction run commits after the journal probe is not written again", async () => {
|
|
133
|
+
await withTempDir(async (dir) => {
|
|
134
|
+
const config = parseConfig({ memoryDir: dir });
|
|
135
|
+
const storage = new StorageManager(config.memoryDir);
|
|
136
|
+
await storage.ensureDirectories();
|
|
137
|
+
const real = createJournalMemoryWriter(storage);
|
|
138
|
+
const deps = extractDeps(storage, "fact");
|
|
139
|
+
deps.writer = {
|
|
140
|
+
writeSealedMemory: real.writeSealedMemory,
|
|
141
|
+
hasJournalMemoryContent: async (content) => {
|
|
142
|
+
const known = await real.hasJournalMemoryContent(content);
|
|
143
|
+
assert.equal(known, false, "the probe runs before the other writer commits");
|
|
144
|
+
await persistUnderFactDedupWindow(storage.dir, async () => false, () => storage.writeMemory("fact", FACT));
|
|
145
|
+
return known;
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
const result = await runJournalReviewExtraction({
|
|
149
|
+
date: "2026-08-20",
|
|
150
|
+
journalText: FACT,
|
|
151
|
+
source: "memoryDir",
|
|
152
|
+
journalConfig: { extractionMode: "review" },
|
|
153
|
+
deps,
|
|
154
|
+
});
|
|
155
|
+
assert.equal(result.pendingReview, 0);
|
|
156
|
+
assert.equal(result.skipped, 1);
|
|
157
|
+
const copies = (await storage.readAllMemories()).filter((memory) => memory.content.includes(FACT));
|
|
158
|
+
assert.equal(copies.length, 1, "the corpus holds one copy of the fact");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -298,6 +298,7 @@ test("memoryDir source provenance differs only in the attribute value", async ()
|
|
|
298
298
|
test("createJournalMemoryWriter dedups journal-tagged memories beyond the fact hash", async () => {
|
|
299
299
|
const calls: string[] = [];
|
|
300
300
|
const storage = {
|
|
301
|
+
dir: "/tmp/journal-writer-test-unused",
|
|
301
302
|
writeSealedMemory: async () => ({}),
|
|
302
303
|
hasFactContentHash: async (content: string) => {
|
|
303
304
|
calls.push(`hash:${content}`);
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Per-day change detection lives in journal-state.ts (timeline.json);
|
|
12
12
|
* this module is the pure pass over one day's POST-STRIP text.
|
|
13
13
|
*/
|
|
14
|
+
import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
|
|
14
15
|
import { scoreImportance } from "../importance.js";
|
|
15
16
|
import { getVerdictKind, type JudgeCandidate, type JudgeVerdict } from "../extraction-judge.js";
|
|
16
17
|
import { log } from "../logger.js";
|
|
@@ -29,6 +30,7 @@ import type { ActivityTimelineJournalConfig } from "./types.js";
|
|
|
29
30
|
|
|
30
31
|
/** The subset of StorageManager the journal pass writes through. */
|
|
31
32
|
export interface JournalStorageIo {
|
|
33
|
+
readonly dir: string;
|
|
32
34
|
writeSealedMemory(
|
|
33
35
|
envelope: SealedMemoryEnvelope,
|
|
34
36
|
extras: { status: MemoryStatus; contentHashSource: string; importance?: ImportanceScore },
|
|
@@ -38,7 +40,14 @@ export interface JournalStorageIo {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export interface JournalMemoryWriter {
|
|
41
|
-
|
|
43
|
+
/**
|
|
44
|
+
* `duplicate` is true when a concurrent writer committed the same fact
|
|
45
|
+
* first and nothing was written.
|
|
46
|
+
*/
|
|
47
|
+
writeSealedMemory(
|
|
48
|
+
envelope: SealedMemoryEnvelope,
|
|
49
|
+
extras: { status: MemoryStatus; contentHashSource: string; importance?: ImportanceScore },
|
|
50
|
+
): Promise<{ tombstoneBlocked?: boolean; duplicate?: boolean }>;
|
|
42
51
|
/** Dedup beyond the fact-only hash index: scans journal-tagged memories. */
|
|
43
52
|
hasJournalMemoryContent(content: string): Promise<boolean>;
|
|
44
53
|
}
|
|
@@ -51,7 +60,19 @@ export interface JournalMemoryWriter {
|
|
|
51
60
|
*/
|
|
52
61
|
export function createJournalMemoryWriter(storage: JournalStorageIo): JournalMemoryWriter {
|
|
53
62
|
return {
|
|
54
|
-
writeSealedMemory:
|
|
63
|
+
writeSealedMemory: async (envelope, extras) => {
|
|
64
|
+
// Facts register in the fact hash index, so extraction and this pass can
|
|
65
|
+
// commit the same new fact. Commit it under the same window extraction
|
|
66
|
+
// uses and recheck the index there. The scan in hasJournalMemoryContent
|
|
67
|
+
// stays outside: it is corpus-sized and the window is bounded.
|
|
68
|
+
if (envelope.category !== "fact") return storage.writeSealedMemory(envelope, extras);
|
|
69
|
+
const outcome = await persistUnderFactDedupWindow(
|
|
70
|
+
storage.dir,
|
|
71
|
+
() => storage.hasFactContentHash(extras.contentHashSource),
|
|
72
|
+
() => storage.writeSealedMemory(envelope, extras),
|
|
73
|
+
);
|
|
74
|
+
return outcome.suppressed ? { duplicate: true } : outcome.result;
|
|
75
|
+
},
|
|
55
76
|
hasJournalMemoryContent: async (content: string) => {
|
|
56
77
|
if (await storage.hasFactContentHash(content)) return true;
|
|
57
78
|
const needle = stripAttributesSuffix(content);
|
|
@@ -210,7 +231,7 @@ export async function runJournalReviewExtraction(input: {
|
|
|
210
231
|
contentHashSource: normalizedKey,
|
|
211
232
|
importance: scoreImportance(fact.content, fact.category, fact.tags),
|
|
212
233
|
});
|
|
213
|
-
if (write.tombstoneBlocked) {
|
|
234
|
+
if (write.tombstoneBlocked || write.duplicate) {
|
|
214
235
|
result.skipped += 1;
|
|
215
236
|
continue;
|
|
216
237
|
}
|
package/src/cli.ts
CHANGED
|
@@ -6107,8 +6107,7 @@ export function registerCli(
|
|
|
6107
6107
|
priorState: state,
|
|
6108
6108
|
syncFn: (cursor) =>
|
|
6109
6109
|
connector.syncIncremental({
|
|
6110
|
-
cursor,
|
|
6111
|
-
config: validatedCfg,
|
|
6110
|
+
cursor, config: validatedCfg, resolveSecretRef: hostSecretRefResolver(registerOptions),
|
|
6112
6111
|
}),
|
|
6113
6112
|
ingestFn: sharedIngestFn,
|
|
6114
6113
|
writeCursorFn: makeWriteCursorFn(name),
|
package/src/config.test.ts
CHANGED
|
@@ -1891,6 +1891,23 @@ test("parseConfig connectors.googleDrive accepts valid overrides", () => {
|
|
|
1891
1891
|
]);
|
|
1892
1892
|
});
|
|
1893
1893
|
|
|
1894
|
+
test("parseConfig connectors.googleDrive keeps a SecretRef credential verbatim", () => {
|
|
1895
|
+
const secretRef = { source: "keychain", id: "projects/drive-secret" };
|
|
1896
|
+
const result = parseConfig({
|
|
1897
|
+
openaiApiKey: "sk-test",
|
|
1898
|
+
connectors: {
|
|
1899
|
+
googleDrive: {
|
|
1900
|
+
enabled: true,
|
|
1901
|
+
clientId: "synthetic-client",
|
|
1902
|
+
clientSecret: secretRef,
|
|
1903
|
+
refreshToken: "synthetic-token",
|
|
1904
|
+
},
|
|
1905
|
+
},
|
|
1906
|
+
});
|
|
1907
|
+
assert.deepEqual(result.connectors.googleDrive.clientSecret, secretRef);
|
|
1908
|
+
assert.equal(result.connectors.googleDrive.clientId, "synthetic-client");
|
|
1909
|
+
});
|
|
1910
|
+
|
|
1894
1911
|
test("parseConfig rejects malformed connectors top-level", () => {
|
|
1895
1912
|
assert.throws(
|
|
1896
1913
|
() => parseConfig({ openaiApiKey: "sk-test", connectors: "nope" }),
|
package/src/config.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
RecallSectionConfig,
|
|
20
20
|
ReasoningEffort,
|
|
21
21
|
SemanticChunkingConfigShape,
|
|
22
|
+
SecretRef,
|
|
22
23
|
SessionObserverBandConfig,
|
|
23
24
|
SlotBehaviorConfig,
|
|
24
25
|
SlotMismatchMode,
|
|
@@ -3719,16 +3720,33 @@ export function parseConfig(
|
|
|
3719
3720
|
? (rawConnectors.googleDrive as Record<string, unknown>)
|
|
3720
3721
|
: {};
|
|
3721
3722
|
const driveEnabled = coerceBool(rawDrive.enabled) === true;
|
|
3723
|
+
const driveClientIdRaw = rawDrive.clientId;
|
|
3724
|
+
// SAFETY: a string value is a string; an object with a string source is a SecretRef, kept verbatim.
|
|
3722
3725
|
const driveClientId =
|
|
3723
|
-
|
|
3726
|
+
Object.prototype.toString.call(driveClientIdRaw) === "[object String]"
|
|
3727
|
+
? (driveClientIdRaw as string)
|
|
3728
|
+
: Object.prototype.toString.call(driveClientIdRaw) === "[object Object]" &&
|
|
3729
|
+
Object.prototype.toString.call((driveClientIdRaw as { source?: object }).source) === "[object String]"
|
|
3730
|
+
? (driveClientIdRaw as SecretRef)
|
|
3731
|
+
: "";
|
|
3732
|
+
const driveClientSecretRaw = rawDrive[CLIENT_SECRET_FIELD];
|
|
3733
|
+
// SAFETY: a string value is a string; an object with a string source is a SecretRef, kept verbatim.
|
|
3724
3734
|
const driveClientSecret =
|
|
3725
|
-
|
|
3726
|
-
?
|
|
3727
|
-
: ""
|
|
3735
|
+
Object.prototype.toString.call(driveClientSecretRaw) === "[object String]"
|
|
3736
|
+
? (driveClientSecretRaw as string)
|
|
3737
|
+
: Object.prototype.toString.call(driveClientSecretRaw) === "[object Object]" &&
|
|
3738
|
+
Object.prototype.toString.call((driveClientSecretRaw as { source?: object }).source) === "[object String]"
|
|
3739
|
+
? (driveClientSecretRaw as SecretRef)
|
|
3740
|
+
: "";
|
|
3741
|
+
const driveRefreshTokenRaw = rawDrive[REFRESH_TOKEN_FIELD];
|
|
3742
|
+
// SAFETY: a string value is a string; an object with a string source is a SecretRef, kept verbatim.
|
|
3728
3743
|
const driveRefreshToken =
|
|
3729
|
-
|
|
3730
|
-
?
|
|
3731
|
-
: ""
|
|
3744
|
+
Object.prototype.toString.call(driveRefreshTokenRaw) === "[object String]"
|
|
3745
|
+
? (driveRefreshTokenRaw as string)
|
|
3746
|
+
: Object.prototype.toString.call(driveRefreshTokenRaw) === "[object Object]" &&
|
|
3747
|
+
Object.prototype.toString.call((driveRefreshTokenRaw as { source?: object }).source) === "[object String]"
|
|
3748
|
+
? (driveRefreshTokenRaw as SecretRef)
|
|
3749
|
+
: "";
|
|
3732
3750
|
const drivePollCoerced = coerceNumber(rawDrive.pollIntervalMs);
|
|
3733
3751
|
let drivePollIntervalMs = 300_000;
|
|
3734
3752
|
if (drivePollCoerced !== undefined) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ResolveSecretRefFn } from "../../resolve-auth-token.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @remnic/core — Live Connectors Framework (issue #683 PR 1/N)
|
|
3
5
|
*
|
|
@@ -139,6 +141,12 @@ export interface SyncIncrementalArgs {
|
|
|
139
141
|
readonly config: ConnectorConfig;
|
|
140
142
|
/** Optional abort signal. Connectors SHOULD honor it for cooperative cancellation. */
|
|
141
143
|
readonly abortSignal?: AbortSignal;
|
|
144
|
+
/**
|
|
145
|
+
* Host resolver for credential fields that hold a SecretRef. A connector
|
|
146
|
+
* resolves those at poll time. Absent a resolver, a plain string still works
|
|
147
|
+
* and a SecretRef fails that connector only.
|
|
148
|
+
*/
|
|
149
|
+
readonly resolveSecretRef?: ResolveSecretRefFn | null;
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
/**
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type GoogleDriveClientFactory,
|
|
15
15
|
type GoogleDriveSyncResult,
|
|
16
16
|
} from "./index.js";
|
|
17
|
+
import { SECRET_RESOLVE_DEADLINE_MS } from "./google-drive.js";
|
|
17
18
|
import type { ConnectorConfig, ConnectorCursor } from "./framework.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -88,6 +89,79 @@ test("validateGoogleDriveConfig accepts a minimal valid config", () => {
|
|
|
88
89
|
assert.deepEqual([...cfg.folderIds], []);
|
|
89
90
|
});
|
|
90
91
|
|
|
92
|
+
test("a SecretRef credential resolves at poll time and never reaches the persisted config", async () => {
|
|
93
|
+
const client = makeMockClient({});
|
|
94
|
+
let seenSecret = "";
|
|
95
|
+
const factory: GoogleDriveClientFactory = async (config) => {
|
|
96
|
+
seenSecret = config.clientSecret;
|
|
97
|
+
return client;
|
|
98
|
+
};
|
|
99
|
+
const connector = createGoogleDriveConnector({ clientFactory: factory });
|
|
100
|
+
const ref = { source: "keychain", id: "projects/drive-secret" };
|
|
101
|
+
const config: ConnectorConfig = { ...SYNTHETIC_CREDS, clientSecret: ref };
|
|
102
|
+
await connector.syncIncremental({
|
|
103
|
+
cursor: null,
|
|
104
|
+
config,
|
|
105
|
+
resolveSecretRef: async () => "resolved-drive-secret",
|
|
106
|
+
});
|
|
107
|
+
assert.equal(seenSecret, "resolved-drive-secret");
|
|
108
|
+
const persisted = JSON.stringify(connector.persistConfig?.(config) ?? {});
|
|
109
|
+
assert.equal(persisted.includes("resolved-drive-secret"), false);
|
|
110
|
+
assert.equal(persisted.includes("projects/drive-secret"), false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("a SecretRef with no resolver fails the connector and hides the reference", async () => {
|
|
114
|
+
const connector = createGoogleDriveConnector({ clientFactory: makeFactory(makeMockClient({})) });
|
|
115
|
+
const config: ConnectorConfig = { ...SYNTHETIC_CREDS, clientSecret: { source: "keychain", id: "projects/drive-secret" } };
|
|
116
|
+
let message = "";
|
|
117
|
+
try {
|
|
118
|
+
await connector.syncIncremental({ cursor: null, config });
|
|
119
|
+
} catch (err) {
|
|
120
|
+
message = err instanceof Error ? err.message : "";
|
|
121
|
+
}
|
|
122
|
+
assert.match(message, /clientSecret is a secret reference but no resolver is configured/);
|
|
123
|
+
assert.equal(message.includes("projects/drive-secret"), false);
|
|
124
|
+
assert.equal(message.includes("keychain"), false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const DRIVE_REF_CONFIG: ConnectorConfig = {
|
|
128
|
+
...SYNTHETIC_CREDS,
|
|
129
|
+
clientSecret: { source: "keychain", id: "projects/drive-secret" },
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
async function driveSyncError(args: { resolveSecretRef: () => Promise<string | undefined>; abortSignal?: AbortSignal }): Promise<Error> {
|
|
133
|
+
const connector = createGoogleDriveConnector({ clientFactory: makeFactory(makeMockClient({})) });
|
|
134
|
+
try {
|
|
135
|
+
await connector.syncIncremental({ cursor: null, config: DRIVE_REF_CONFIG, ...args });
|
|
136
|
+
} catch (err) {
|
|
137
|
+
return err instanceof Error ? err : new Error("non-error thrown");
|
|
138
|
+
}
|
|
139
|
+
return new Error("sync did not fail");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
test("a SecretRef resolver that never settles is cut off by the abort signal", async () => {
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
const failure = driveSyncError({ resolveSecretRef: () => new Promise(() => undefined), abortSignal: controller.signal });
|
|
145
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
146
|
+
controller.abort();
|
|
147
|
+
assert.equal((await failure).name, "AbortError");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("a SecretRef resolver that never settles fails at the deadline", async (t) => {
|
|
151
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
152
|
+
const failure = driveSyncError({ resolveSecretRef: () => new Promise(() => undefined) });
|
|
153
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
154
|
+
t.mock.timers.tick(SECRET_RESOLVE_DEADLINE_MS);
|
|
155
|
+
const err = await failure;
|
|
156
|
+
assert.match(err.message, /clientSecret secret reference was not resolved within 30000 ms/);
|
|
157
|
+
assert.equal(err.message.includes("projects/drive-secret"), false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a SecretRef that resolves to no value says the resolver may be unavailable", async () => {
|
|
161
|
+
const err = await driveSyncError({ resolveSecretRef: async () => undefined });
|
|
162
|
+
assert.match(err.message, /resolved to no value \(the host secret resolver may be unavailable\)/);
|
|
163
|
+
});
|
|
164
|
+
|
|
91
165
|
test("validateGoogleDriveConfig rejects non-object input", () => {
|
|
92
166
|
assert.throws(() => validateGoogleDriveConfig(null), /must be an object/);
|
|
93
167
|
assert.throws(() => validateGoogleDriveConfig([]), /must be an object/);
|
|
@@ -64,7 +64,9 @@ import type {
|
|
|
64
64
|
SyncIncrementalResult,
|
|
65
65
|
} from "./framework.js";
|
|
66
66
|
import { isTransientHttpError } from "./transient-errors.js";
|
|
67
|
-
import { throwIfAborted } from "../../abort-error.js";
|
|
67
|
+
import { raceAbort, throwIfAborted } from "../../abort-error.js";
|
|
68
|
+
import type { ResolveSecretRefFn } from "../../resolve-auth-token.js";
|
|
69
|
+
import type { SecretRef } from "../../types.js";
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
72
|
* Stable connector id. Lives in the registry under this exact string.
|
|
@@ -122,15 +124,40 @@ const FOLDER_ID_PATTERN = /^[A-Za-z0-9_-]{8,256}$/;
|
|
|
122
124
|
* Validated, frozen view of `connectors.googleDrive.*`.
|
|
123
125
|
*/
|
|
124
126
|
export interface GoogleDriveConnectorConfig {
|
|
127
|
+
readonly clientId: DriveCredential;
|
|
128
|
+
readonly clientSecret: DriveCredential;
|
|
129
|
+
readonly refreshToken: DriveCredential;
|
|
130
|
+
/** Poll interval surfaced to the scheduler. */
|
|
131
|
+
readonly pollIntervalMs: number;
|
|
132
|
+
/** Folder ids to scope import to. Empty = "all accessible". */
|
|
133
|
+
readonly folderIds: readonly string[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** A credential field as configured: a plain string, or a SecretRef resolved at poll time. */
|
|
137
|
+
type DriveCredential = string | SecretRef;
|
|
138
|
+
|
|
139
|
+
interface DriveCredentialFields {
|
|
140
|
+
clientId: DriveCredential;
|
|
141
|
+
clientSecret: DriveCredential;
|
|
142
|
+
refreshToken: DriveCredential;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Resolved credentials passed to the Drive client. Always plain strings. */
|
|
146
|
+
interface GoogleDriveResolvedConfig {
|
|
125
147
|
readonly clientId: string;
|
|
126
148
|
readonly clientSecret: string;
|
|
127
149
|
readonly refreshToken: string;
|
|
128
|
-
/** Poll interval surfaced to the scheduler. */
|
|
129
150
|
readonly pollIntervalMs: number;
|
|
130
|
-
/** Folder ids to scope import to. Empty = "all accessible". */
|
|
131
151
|
readonly folderIds: readonly string[];
|
|
132
152
|
}
|
|
133
153
|
|
|
154
|
+
/** Disk form of a Drive config. A SecretRef clientId is omitted, never written. */
|
|
155
|
+
interface PersistedGoogleDriveConfig {
|
|
156
|
+
pollIntervalMs: number;
|
|
157
|
+
folderIds: readonly string[];
|
|
158
|
+
clientId?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
134
161
|
/**
|
|
135
162
|
* Optional injection point for tests. The real connector dynamically imports
|
|
136
163
|
* `googleapis`; tests pass a stub here to avoid the optional-peer-dep
|
|
@@ -139,7 +166,7 @@ export interface GoogleDriveConnectorConfig {
|
|
|
139
166
|
* The shape only covers the tiny slice of the SDK we actually use.
|
|
140
167
|
*/
|
|
141
168
|
export interface GoogleDriveClientFactory {
|
|
142
|
-
(config:
|
|
169
|
+
(config: GoogleDriveResolvedConfig): Promise<GoogleDriveClient>;
|
|
143
170
|
}
|
|
144
171
|
|
|
145
172
|
/**
|
|
@@ -243,9 +270,30 @@ export function validateGoogleDriveConfig(raw: unknown): GoogleDriveConnectorCon
|
|
|
243
270
|
);
|
|
244
271
|
}
|
|
245
272
|
const r = raw as Record<string, unknown>;
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
273
|
+
const credentialKeys = ["clientId", CLIENT_SECRET_FIELD, REFRESH_TOKEN_FIELD] as const;
|
|
274
|
+
const credentials: DriveCredentialFields = {
|
|
275
|
+
clientId: "",
|
|
276
|
+
clientSecret: "",
|
|
277
|
+
refreshToken: "",
|
|
278
|
+
};
|
|
279
|
+
for (const key of credentialKeys) {
|
|
280
|
+
const value = r[key];
|
|
281
|
+
if (Object.prototype.toString.call(value) === "[object Object]") {
|
|
282
|
+
// SAFETY: the toString check proves value is a non-null object.
|
|
283
|
+
const source = (value as { source?: object }).source;
|
|
284
|
+
if (Object.prototype.toString.call(source) === "[object String]") {
|
|
285
|
+
// SAFETY: a string `source` on this object is the SecretRef discriminator.
|
|
286
|
+
credentials[key] = value as SecretRef;
|
|
287
|
+
} else {
|
|
288
|
+
credentials[key] = requireNonEmptyString(value, key);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
credentials[key] = requireNonEmptyString(value, key);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const clientId = credentials.clientId;
|
|
295
|
+
const clientSecret = credentials.clientSecret;
|
|
296
|
+
const refreshToken = credentials.refreshToken;
|
|
249
297
|
|
|
250
298
|
let pollIntervalMs: number;
|
|
251
299
|
if (r.pollIntervalMs === undefined) {
|
|
@@ -321,6 +369,76 @@ function requireNonEmptyString(value: unknown, key: string): string {
|
|
|
321
369
|
return trimmed;
|
|
322
370
|
}
|
|
323
371
|
|
|
372
|
+
|
|
373
|
+
/** Longest wait for the host secret resolver, so a stalled secret store cannot hold the sync loop. */
|
|
374
|
+
export const SECRET_RESOLVE_DEADLINE_MS = 30_000;
|
|
375
|
+
|
|
376
|
+
async function resolveWithinDeadline(
|
|
377
|
+
ref: SecretRef,
|
|
378
|
+
key: string,
|
|
379
|
+
resolver: ResolveSecretRefFn,
|
|
380
|
+
abortSignal: AbortSignal | undefined,
|
|
381
|
+
): Promise<string | undefined> {
|
|
382
|
+
throwIfAborted(abortSignal, "googleDrive: sync aborted");
|
|
383
|
+
const deadline = new AbortController();
|
|
384
|
+
const timer = setTimeout(() => deadline.abort(), SECRET_RESOLVE_DEADLINE_MS);
|
|
385
|
+
const signal = abortSignal ? AbortSignal.any([abortSignal, deadline.signal]) : deadline.signal;
|
|
386
|
+
try {
|
|
387
|
+
return await raceAbort(Promise.resolve(resolver(ref)), signal, "googleDrive: secret resolve aborted");
|
|
388
|
+
} catch {
|
|
389
|
+
throwIfAborted(abortSignal, "googleDrive: sync aborted");
|
|
390
|
+
if (deadline.signal.aborted) {
|
|
391
|
+
throw new Error(`googleDrive: ${key} secret reference was not resolved within ${SECRET_RESOLVE_DEADLINE_MS} ms`);
|
|
392
|
+
}
|
|
393
|
+
throw new Error(`googleDrive: ${key} secret reference could not be resolved`);
|
|
394
|
+
} finally {
|
|
395
|
+
clearTimeout(timer);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function resolveCredential(
|
|
400
|
+
value: DriveCredential,
|
|
401
|
+
key: string,
|
|
402
|
+
resolver: ResolveSecretRefFn | null | undefined,
|
|
403
|
+
abortSignal: AbortSignal | undefined,
|
|
404
|
+
): Promise<string> {
|
|
405
|
+
if (Object.prototype.toString.call(value) !== "[object Object]") {
|
|
406
|
+
// SAFETY: a DriveCredential that is not an object is the plain string form.
|
|
407
|
+
return value as string;
|
|
408
|
+
}
|
|
409
|
+
// SAFETY: the toString check proves value is a non-null object, so it is a SecretRef.
|
|
410
|
+
const ref = value as SecretRef;
|
|
411
|
+
if (resolver == null) {
|
|
412
|
+
throw new Error(`googleDrive: ${key} is a secret reference but no resolver is configured`);
|
|
413
|
+
}
|
|
414
|
+
const resolved = await resolveWithinDeadline(ref, key, resolver, abortSignal);
|
|
415
|
+
if (resolved === undefined) {
|
|
416
|
+
throw new Error(`googleDrive: ${key} secret reference resolved to no value (the host secret resolver may be unavailable)`);
|
|
417
|
+
}
|
|
418
|
+
if (Object.prototype.toString.call(resolved) !== "[object String]") {
|
|
419
|
+
throw new Error(`googleDrive: ${key} secret reference resolved to a non-string value`);
|
|
420
|
+
}
|
|
421
|
+
const text = resolved.trim();
|
|
422
|
+
if (text.length === 0) {
|
|
423
|
+
throw new Error(`googleDrive: ${key} secret reference resolved to an empty value`);
|
|
424
|
+
}
|
|
425
|
+
return text;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function resolveGoogleDriveConfig(
|
|
429
|
+
stored: GoogleDriveConnectorConfig,
|
|
430
|
+
resolver: ResolveSecretRefFn | null | undefined,
|
|
431
|
+
abortSignal: AbortSignal | undefined,
|
|
432
|
+
): Promise<GoogleDriveResolvedConfig> {
|
|
433
|
+
return {
|
|
434
|
+
clientId: await resolveCredential(stored.clientId, "clientId", resolver, abortSignal),
|
|
435
|
+
[CLIENT_SECRET_FIELD]: await resolveCredential(stored[CLIENT_SECRET_FIELD], CLIENT_SECRET_FIELD, resolver, abortSignal),
|
|
436
|
+
[REFRESH_TOKEN_FIELD]: await resolveCredential(stored[REFRESH_TOKEN_FIELD], REFRESH_TOKEN_FIELD, resolver, abortSignal),
|
|
437
|
+
pollIntervalMs: stored.pollIntervalMs,
|
|
438
|
+
folderIds: stored.folderIds,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
324
442
|
/**
|
|
325
443
|
* Decide whether a Drive `change` corresponds to an importable text file
|
|
326
444
|
* scoped to (optionally) the configured folder set.
|
|
@@ -405,16 +523,25 @@ export function createGoogleDriveConnector(
|
|
|
405
523
|
|
|
406
524
|
persistConfig(validated: ConnectorConfig): ConnectorConfig {
|
|
407
525
|
const config = validateGoogleDriveConfig(validated);
|
|
408
|
-
|
|
409
|
-
clientId: config.clientId,
|
|
526
|
+
const persisted: PersistedGoogleDriveConfig = {
|
|
410
527
|
pollIntervalMs: config.pollIntervalMs,
|
|
411
528
|
folderIds: config.folderIds,
|
|
412
|
-
}
|
|
529
|
+
};
|
|
530
|
+
if (Object.prototype.toString.call(config.clientId) === "[object String]") {
|
|
531
|
+
// SAFETY: the toString check proves clientId is a string, not a SecretRef.
|
|
532
|
+
persisted.clientId = config.clientId as string;
|
|
533
|
+
}
|
|
534
|
+
return Object.freeze(persisted);
|
|
535
|
+
|
|
413
536
|
},
|
|
414
537
|
|
|
415
538
|
async syncIncremental(args: SyncIncrementalArgs): Promise<SyncIncrementalResult> {
|
|
416
539
|
// Re-validate on every pass: a JS caller could mutate it between passes.
|
|
417
|
-
const config =
|
|
540
|
+
const config = await resolveGoogleDriveConfig(
|
|
541
|
+
validateGoogleDriveConfig(args.config),
|
|
542
|
+
args.resolveSecretRef,
|
|
543
|
+
args.abortSignal,
|
|
544
|
+
);
|
|
418
545
|
throwIfAborted(args.abortSignal, "googleDrive: sync aborted");
|
|
419
546
|
|
|
420
547
|
const client = await clientFactory(config);
|