@remnic/core 1.1.12 → 1.1.13
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-cli.d.ts +2 -1
- package/dist/access-cli.js +263 -82
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +26 -60
- package/dist/access-http.js +43 -29
- package/dist/access-mcp.d.ts +24 -6
- package/dist/access-mcp.js +35 -28
- package/dist/access-schema.d.ts +9 -6
- package/dist/access-schema.js +7 -5
- package/dist/access-service-DcCDmNYC.d.ts +1542 -0
- package/dist/access-service.d.ts +25 -7
- package/dist/access-service.js +33 -26
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.js +11 -3
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/claude-code.d.ts +24 -0
- package/dist/adapters/claude-code.js +9 -0
- package/dist/adapters/codex.d.ts +25 -0
- package/dist/adapters/codex.js +9 -0
- package/dist/adapters/hermes.d.ts +35 -0
- package/dist/adapters/hermes.js +9 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +26 -0
- package/dist/adapters/registry.d.ts +20 -0
- package/dist/adapters/registry.js +13 -0
- package/dist/adapters/replit.d.ts +28 -0
- package/dist/adapters/replit.js +9 -0
- package/dist/adapters/types.d.ts +43 -0
- package/dist/adapters/types.js +8 -0
- package/dist/bootstrap.d.ts +20 -5
- package/dist/boxes.d.ts +7 -0
- package/dist/boxes.js +1 -1
- package/dist/briefing.d.ts +5 -3
- package/dist/briefing.js +9 -6
- package/dist/buffer-surprise-report.js +1 -1
- package/dist/buffer.d.ts +18 -4
- package/dist/buffer.js +1 -1
- package/dist/calibration.js +4 -4
- package/dist/capsule-cli.d.ts +4 -4
- package/dist/capsule-cli.js +1 -1
- package/dist/capsule-crypto-5CYAGVC5.js +18 -0
- package/dist/capsule-merge-4MGKE7C5.js +189 -0
- package/dist/causal-behavior.d.ts +8 -28
- package/dist/causal-behavior.js +6 -3
- package/dist/causal-behavior.js.map +1 -1
- package/dist/causal-chain.js +3 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +24 -13
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +3 -3
- package/dist/causal-trajectory.js +1 -1
- package/dist/chunk-25MQ7IHJ.js +427 -0
- package/dist/chunk-25MQ7IHJ.js.map +1 -0
- package/dist/chunk-2F2W355T.js +256 -0
- package/dist/chunk-2F2W355T.js.map +1 -0
- package/dist/chunk-2KI4QFHU.js +228 -0
- package/dist/chunk-2KI4QFHU.js.map +1 -0
- package/dist/chunk-2PRQG7PV.js +86 -0
- package/dist/chunk-2PRQG7PV.js.map +1 -0
- package/dist/chunk-2QR3XXIC.js +2272 -0
- package/dist/chunk-2QR3XXIC.js.map +1 -0
- package/dist/chunk-2WWLHTZY.js +121 -0
- package/dist/chunk-326G7DJK.js +2185 -0
- package/dist/chunk-326G7DJK.js.map +1 -0
- package/dist/chunk-34DQE4KF.js +174 -0
- package/dist/chunk-34DQE4KF.js.map +1 -0
- package/dist/chunk-3APJ5EVB.js +601 -0
- package/dist/chunk-3APJ5EVB.js.map +1 -0
- package/dist/chunk-3HPAPHUK.js +51 -0
- package/dist/chunk-3HPAPHUK.js.map +1 -0
- package/dist/chunk-3JXBXXM2.js +69 -0
- package/dist/chunk-3JXBXXM2.js.map +1 -0
- package/dist/chunk-3KW65B36.js +681 -0
- package/dist/chunk-3KW65B36.js.map +1 -0
- package/dist/chunk-3UXOZBHV.js +20 -0
- package/dist/chunk-3UXOZBHV.js.map +1 -0
- package/dist/chunk-3VAL7ZL2.js +266 -0
- package/dist/chunk-3VAL7ZL2.js.map +1 -0
- package/dist/chunk-3Y4P7RXM.js +31 -0
- package/dist/chunk-3Y4P7RXM.js.map +1 -0
- package/dist/chunk-47VWKCAF.js +273 -0
- package/dist/chunk-47VWKCAF.js.map +1 -0
- package/dist/chunk-4CRG46BG.js +271 -0
- package/dist/chunk-5375UYTQ.js +914 -0
- package/dist/chunk-5375UYTQ.js.map +1 -0
- package/dist/chunk-56K5QLHX.js +506 -0
- package/dist/chunk-56K5QLHX.js.map +1 -0
- package/dist/chunk-5RGLBDQF.js +596 -0
- package/dist/chunk-5RGLBDQF.js.map +1 -0
- package/dist/chunk-5UZXUTVO.js +9 -0
- package/dist/chunk-5UZXUTVO.js.map +1 -0
- package/dist/chunk-65PG43EQ.js +105 -0
- package/dist/chunk-65PG43EQ.js.map +1 -0
- package/dist/chunk-66DHUKLO.js +57 -0
- package/dist/chunk-66DHUKLO.js.map +1 -0
- package/dist/chunk-6FC5EGNV.js +46 -0
- package/dist/chunk-6FC5EGNV.js.map +1 -0
- package/dist/chunk-6H2TESSP.js +62 -0
- package/dist/chunk-6H2TESSP.js.map +1 -0
- package/dist/chunk-6LVVDPJ4.js +32 -0
- package/dist/chunk-6LVVDPJ4.js.map +1 -0
- package/dist/chunk-6RVI47ZR.js +159 -0
- package/dist/chunk-6RVI47ZR.js.map +1 -0
- package/dist/chunk-7AAT6G4Q.js +5117 -0
- package/dist/chunk-7AAT6G4Q.js.map +1 -0
- package/dist/chunk-7DTASS5T.js +29 -0
- package/dist/chunk-7DTASS5T.js.map +1 -0
- package/dist/chunk-7IASACLB.js +596 -0
- package/dist/chunk-7MNMYOFP.js +32 -0
- package/dist/chunk-7MNMYOFP.js.map +1 -0
- package/dist/chunk-7N4KAIGN.js +133 -0
- package/dist/chunk-7N4KAIGN.js.map +1 -0
- package/dist/chunk-7OZ53EXP.js +101 -0
- package/dist/chunk-7OZ53EXP.js.map +1 -0
- package/dist/chunk-7XYTQGCC.js +134 -0
- package/dist/chunk-7XYTQGCC.js.map +1 -0
- package/dist/chunk-A2XUIMJ3.js +341 -0
- package/dist/chunk-A2XUIMJ3.js.map +1 -0
- package/dist/chunk-AGZQD76C.js +201 -0
- package/dist/chunk-AGZQD76C.js.map +1 -0
- package/dist/chunk-APO3DCMU.js +361 -0
- package/dist/chunk-APO3DCMU.js.map +1 -0
- package/dist/chunk-BFBF3XEF.js +283 -0
- package/dist/chunk-BFBF3XEF.js.map +1 -0
- package/dist/chunk-BJ3KMYTB.js +1974 -0
- package/dist/chunk-BJ3KMYTB.js.map +1 -0
- package/dist/chunk-CHEL3SKB.js +6758 -0
- package/dist/chunk-CHEL3SKB.js.map +1 -0
- package/dist/chunk-CQZRLNMV.js +1491 -0
- package/dist/chunk-CQZRLNMV.js.map +1 -0
- package/dist/chunk-D46YSIYX.js +892 -0
- package/dist/chunk-D46YSIYX.js.map +1 -0
- package/dist/chunk-DINWEURR.js +648 -0
- package/dist/chunk-DINWEURR.js.map +1 -0
- package/dist/chunk-DK5LDEQM.js +530 -0
- package/dist/chunk-DK5LDEQM.js.map +1 -0
- package/dist/chunk-DOM4GKSW.js +34 -0
- package/dist/chunk-DOM4GKSW.js.map +1 -0
- package/dist/chunk-EDTHC6UD.js +1075 -0
- package/dist/chunk-EFJ3MQ4V.js +721 -0
- package/dist/chunk-EHRTFRWW.js +89 -0
- package/dist/chunk-EHRTFRWW.js.map +1 -0
- package/dist/chunk-FAJ7FZYM.js +11 -0
- package/dist/chunk-FAJ7FZYM.js.map +1 -0
- package/dist/chunk-FBYESMQ2.js +570 -0
- package/dist/chunk-FDU6HUUL.js +147 -0
- package/dist/chunk-FF4KLI5W.js +99 -0
- package/dist/chunk-FF4KLI5W.js.map +1 -0
- package/dist/chunk-FIT6DMX6.js +310 -0
- package/dist/chunk-FIT6DMX6.js.map +1 -0
- package/dist/chunk-FJ43PRLT.js +272 -0
- package/dist/chunk-FJ43PRLT.js.map +1 -0
- package/dist/chunk-FKFMOY3N.js +32 -0
- package/dist/chunk-FKFMOY3N.js.map +1 -0
- package/dist/chunk-FLTNHQK6.js +262 -0
- package/dist/chunk-FLTNHQK6.js.map +1 -0
- package/dist/chunk-GA454ALV.js +12436 -0
- package/dist/chunk-GA454ALV.js.map +1 -0
- package/dist/chunk-GGKRUQOO.js +228 -0
- package/dist/chunk-GIF42EW3.js +63 -0
- package/dist/chunk-GIF42EW3.js.map +1 -0
- package/dist/chunk-GL6I6MEQ.js +647 -0
- package/dist/chunk-H3ME6L6D.js +709 -0
- package/dist/chunk-H3ME6L6D.js.map +1 -0
- package/dist/chunk-HHLLAQGZ.js +1 -0
- package/dist/chunk-HXXBL2KD.js +2040 -0
- package/dist/chunk-I5V2VDIW.js +219 -0
- package/dist/chunk-I5V2VDIW.js.map +1 -0
- package/dist/chunk-I6K5FBRQ.js +35 -0
- package/dist/chunk-I6K5FBRQ.js.map +1 -0
- package/dist/chunk-ICRIXAP2.js +121 -0
- package/dist/chunk-ICRIXAP2.js.map +1 -0
- package/dist/chunk-J4EB7DNW.js +11 -0
- package/dist/chunk-J4EB7DNW.js.map +1 -0
- package/dist/chunk-JLFA7DQG.js +62 -0
- package/dist/chunk-JLFA7DQG.js.map +1 -0
- package/dist/chunk-KJTKLXTH.js +9 -0
- package/dist/chunk-KJTKLXTH.js.map +1 -0
- package/dist/chunk-KLAO5DGL.js +917 -0
- package/dist/chunk-KLAO5DGL.js.map +1 -0
- package/dist/chunk-KNKUID7G.js +183 -0
- package/dist/chunk-KOSORCJG.js +624 -0
- package/dist/chunk-KOSORCJG.js.map +1 -0
- package/dist/chunk-KUJVMMZQ.js +1262 -0
- package/dist/chunk-KUJVMMZQ.js.map +1 -0
- package/dist/chunk-LCR46JY5.js +123 -0
- package/dist/chunk-LCR46JY5.js.map +1 -0
- package/dist/chunk-LLQ2LLWF.js +148 -0
- package/dist/chunk-LLQ2LLWF.js.map +1 -0
- package/dist/chunk-LPMVBPA3.js +236 -0
- package/dist/chunk-LT3NLYSI.js +50 -0
- package/dist/chunk-LT3NLYSI.js.map +1 -0
- package/dist/chunk-LUDTDZLK.js +287 -0
- package/dist/chunk-LUDTDZLK.js.map +1 -0
- package/dist/chunk-M23FSH32.js +3963 -0
- package/dist/chunk-M23FSH32.js.map +1 -0
- package/dist/chunk-MC26UJIM.js +118 -0
- package/dist/chunk-ME6ESPZU.js +119 -0
- package/dist/chunk-ME6ESPZU.js.map +1 -0
- package/dist/chunk-MGKYQQYF.js +272 -0
- package/dist/chunk-MJFNCJXV.js +66 -0
- package/dist/chunk-MJFNCJXV.js.map +1 -0
- package/dist/chunk-MSWG7JI6.js +237 -0
- package/dist/chunk-MSWG7JI6.js.map +1 -0
- package/dist/chunk-MT25YHYH.js +141 -0
- package/dist/chunk-MT25YHYH.js.map +1 -0
- package/dist/chunk-MT4HVDUZ.js +53 -0
- package/dist/chunk-MY6TPVXW.js +219 -0
- package/dist/chunk-N2D6GXBM.js +267 -0
- package/dist/chunk-N2D6GXBM.js.map +1 -0
- package/dist/chunk-NJ3MJQZX.js +46 -0
- package/dist/chunk-NJ3MJQZX.js.map +1 -0
- package/dist/chunk-NMZY542O.js +335 -0
- package/dist/chunk-NMZY542O.js.map +1 -0
- package/dist/chunk-NNVTUXEB.js +23 -0
- package/dist/chunk-NZL6GGQE.js +375 -0
- package/dist/chunk-NZL6GGQE.js.map +1 -0
- package/dist/chunk-P4NEIHUT.js +108 -0
- package/dist/chunk-P7FMDTKL.js +103 -0
- package/dist/chunk-P7FMDTKL.js.map +1 -0
- package/dist/chunk-PHK3HARR.js +32 -0
- package/dist/chunk-PHK3HARR.js.map +1 -0
- package/dist/chunk-PIRJPV5T.js +98 -0
- package/dist/chunk-PIRJPV5T.js.map +1 -0
- package/dist/chunk-PK7H5L6Y.js +159 -0
- package/dist/chunk-PK7H5L6Y.js.map +1 -0
- package/dist/chunk-PR5FBTFU.js +233 -0
- package/dist/chunk-PR5FBTFU.js.map +1 -0
- package/dist/chunk-PU63GXWS.js +174 -0
- package/dist/chunk-PU63GXWS.js.map +1 -0
- package/dist/chunk-PZIAX57I.js +124 -0
- package/dist/chunk-PZIAX57I.js.map +1 -0
- package/dist/chunk-Q7P4WJDP.js +26 -0
- package/dist/chunk-Q7P4WJDP.js.map +1 -0
- package/dist/chunk-QQUAB63I.js +63 -0
- package/dist/chunk-QQUAB63I.js.map +1 -0
- package/dist/chunk-QRNI5JBH.js +18 -0
- package/dist/chunk-RHY3HH7P.js +601 -0
- package/dist/chunk-RHY3HH7P.js.map +1 -0
- package/dist/chunk-RRF5UOBJ.js +91 -0
- package/dist/chunk-RXDLTSWT.js +124 -0
- package/dist/chunk-RXDLTSWT.js.map +1 -0
- package/dist/chunk-RYED3SPJ.js +42 -0
- package/dist/chunk-RYED3SPJ.js.map +1 -0
- package/dist/chunk-S7KDBTWT.js +106 -0
- package/dist/chunk-S7KDBTWT.js.map +1 -0
- package/dist/chunk-SEDEKFYQ.js +1 -0
- package/dist/chunk-TECVW3JP.js +36 -0
- package/dist/chunk-TECVW3JP.js.map +1 -0
- package/dist/chunk-TFO23QT4.js +88 -0
- package/dist/chunk-TFO23QT4.js.map +1 -0
- package/dist/chunk-TK4UEOSK.js +76 -0
- package/dist/chunk-TK4UEOSK.js.map +1 -0
- package/dist/chunk-TKWGAOLV.js +122 -0
- package/dist/chunk-TKWGAOLV.js.map +1 -0
- package/dist/chunk-TMM4S4IJ.js +597 -0
- package/dist/chunk-TMM4S4IJ.js.map +1 -0
- package/dist/chunk-TMQLARTH.js +188 -0
- package/dist/chunk-TMQLARTH.js.map +1 -0
- package/dist/chunk-TPDBFYEG.js +130 -0
- package/dist/chunk-TPDBFYEG.js.map +1 -0
- package/dist/chunk-TPMQ3G6Z.js +145 -0
- package/dist/chunk-TPMQ3G6Z.js.map +1 -0
- package/dist/chunk-TZOLIGIG.js +61 -0
- package/dist/chunk-TZOLIGIG.js.map +1 -0
- package/dist/chunk-U3PN77QT.js +113 -0
- package/dist/chunk-U3WSW6PZ.js +277 -0
- package/dist/chunk-U4SCL7B7.js +640 -0
- package/dist/chunk-U4SCL7B7.js.map +1 -0
- package/dist/chunk-UWK5OXUJ.js +156 -0
- package/dist/chunk-UWK5OXUJ.js.map +1 -0
- package/dist/chunk-UWVJF25J.js +74 -0
- package/dist/chunk-UXHQAFNA.js +1317 -0
- package/dist/chunk-UXHQAFNA.js.map +1 -0
- package/dist/chunk-V5OCT34X.js +1 -0
- package/dist/chunk-VLXA6PI2.js +304 -0
- package/dist/chunk-VLXA6PI2.js.map +1 -0
- package/dist/chunk-VNO6ZJ35.js +500 -0
- package/dist/chunk-VNO6ZJ35.js.map +1 -0
- package/dist/chunk-VW676BEI.js +827 -0
- package/dist/chunk-VW676BEI.js.map +1 -0
- package/dist/chunk-W3LR522O.js +2296 -0
- package/dist/chunk-W4L6CZKA.js +96 -0
- package/dist/chunk-W4L6CZKA.js.map +1 -0
- package/dist/chunk-W4RVMTHR.js +372 -0
- package/dist/chunk-W4RVMTHR.js.map +1 -0
- package/dist/chunk-WEHSQBFR.js +188 -0
- package/dist/chunk-WEHSQBFR.js.map +1 -0
- package/dist/chunk-WELDCG6C.js +380 -0
- package/dist/chunk-WELDCG6C.js.map +1 -0
- package/dist/chunk-WZYKANL3.js +2800 -0
- package/dist/chunk-WZYKANL3.js.map +1 -0
- package/dist/chunk-XIG5PDM7.js +48 -0
- package/dist/chunk-XJNBEDFE.js +193 -0
- package/dist/chunk-XJNBEDFE.js.map +1 -0
- package/dist/chunk-XVVIG67A.js +291 -0
- package/dist/chunk-XVVIG67A.js.map +1 -0
- package/dist/chunk-XVZ7B3HG.js +135 -0
- package/dist/chunk-YBPYIAA5.js +73 -0
- package/dist/chunk-YBPYIAA5.js.map +1 -0
- package/dist/chunk-Z734BLO3.js +21 -0
- package/dist/chunk-Z734BLO3.js.map +1 -0
- package/dist/chunk-ZKSK55RC.js +269 -0
- package/dist/chunk-ZKSK55RC.js.map +1 -0
- package/dist/chunk-ZTFCYYEZ.js +69 -0
- package/dist/chunk-ZTFCYYEZ.js.map +1 -0
- package/dist/chunk-ZY2MNJR6.js +329 -0
- package/dist/chunk-ZY2MNJR6.js.map +1 -0
- package/dist/cli-D3VpkVwB.d.ts +1136 -0
- package/dist/cli.d.ts +39 -10
- package/dist/cli.js +108 -49
- package/dist/commitment-ledger.js +1 -1
- package/dist/compat/checks.d.ts +5 -0
- package/dist/compat/checks.js +11 -0
- package/dist/compat/checks.js.map +1 -0
- package/dist/compat/types.d.ts +30 -0
- package/dist/compat/types.js +1 -0
- package/dist/compat/types.js.map +1 -0
- package/dist/compounding/engine.d.ts +221 -0
- package/dist/compounding/engine.js +32 -0
- package/dist/compounding/engine.js.map +1 -0
- package/dist/compounding/preference-consolidator.d.ts +92 -0
- package/dist/compounding/preference-consolidator.js +553 -0
- package/dist/compounding/preference-consolidator.js.map +1 -0
- package/dist/config.d.ts +4 -2
- package/dist/config.js +9 -4
- package/dist/conflict-policy-DyJ2wd-h.d.ts +4 -0
- package/dist/connectors/codex-materialize-runner.d.ts +64 -0
- package/dist/connectors/codex-materialize-runner.js +33 -0
- package/dist/connectors/codex-materialize-runner.js.map +1 -0
- package/dist/connectors/codex-materialize.d.ts +195 -0
- package/dist/connectors/codex-materialize.js +38 -0
- package/dist/connectors/codex-materialize.js.map +1 -0
- package/dist/connectors/index.d.ts +444 -0
- package/dist/connectors/index.js +115 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors-cli-CwbyjGR7.d.ts +257 -0
- package/dist/connectors-cli.d.ts +1 -1
- package/dist/consolidation-provenance-check.d.ts +3 -1
- package/dist/consolidation-undo.d.ts +3 -1
- package/dist/contradiction/index.d.ts +258 -0
- package/dist/contradiction/index.js +43 -0
- package/dist/contradiction/index.js.map +1 -0
- package/dist/contradiction-review-ATP4S6IC.js +30 -0
- package/dist/contradiction-review-ATP4S6IC.js.map +1 -0
- package/dist/contradiction-scan-5A4IDZV5.js +13 -0
- package/dist/contradiction-scan-5A4IDZV5.js.map +1 -0
- package/dist/conversation-index/backend.d.ts +97 -0
- package/dist/conversation-index/backend.js +13 -0
- package/dist/conversation-index/backend.js.map +1 -0
- package/dist/conversation-index/chunker.d.ts +16 -0
- package/dist/conversation-index/chunker.js +8 -0
- package/dist/conversation-index/chunker.js.map +1 -0
- package/dist/conversation-index/cleanup.d.ts +11 -0
- package/dist/conversation-index/cleanup.js +9 -0
- package/dist/conversation-index/cleanup.js.map +1 -0
- package/dist/conversation-index/faiss-adapter.d.ts +6 -0
- package/dist/conversation-index/faiss-adapter.js +16 -0
- package/dist/conversation-index/faiss-adapter.js.map +1 -0
- package/dist/conversation-index/indexer.d.ts +23 -0
- package/dist/conversation-index/indexer.js +15 -0
- package/dist/conversation-index/indexer.js.map +1 -0
- package/dist/conversation-index/search.d.ts +6 -0
- package/dist/conversation-index/search.js +11 -0
- package/dist/conversation-index/search.js.map +1 -0
- package/dist/embedding-fallback.js +2 -2
- package/dist/enrichment/index.d.ts +163 -0
- package/dist/enrichment/index.js +18 -0
- package/dist/enrichment/index.js.map +1 -0
- package/dist/entity-retrieval.d.ts +4 -2
- package/dist/entity-retrieval.js +8 -5
- package/dist/evals.js +1 -1
- package/dist/explicit-capture.d.ts +20 -5
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-judge-training.js +1 -1
- package/dist/extraction.js +8 -8
- package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
- package/dist/fallback-llm.d.ts +2 -0
- package/dist/fallback-llm.js +4 -4
- package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
- package/dist/index.d.ts +66 -711
- package/dist/index.js +556 -2680
- package/dist/index.js.map +1 -1
- package/dist/lcm/archive.d.ts +89 -0
- package/dist/lcm/archive.js +12 -0
- package/dist/lcm/archive.js.map +1 -0
- package/dist/lcm/dag.d.ts +48 -0
- package/dist/lcm/dag.js +8 -0
- package/dist/lcm/dag.js.map +1 -0
- package/dist/lcm/engine.d.ts +116 -0
- package/dist/lcm/engine.js +20 -0
- package/dist/lcm/engine.js.map +1 -0
- package/dist/lcm/index.d.ts +12 -0
- package/dist/lcm/index.js +44 -0
- package/dist/lcm/index.js.map +1 -0
- package/dist/lcm/queue.d.ts +62 -0
- package/dist/lcm/queue.js +8 -0
- package/dist/lcm/queue.js.map +1 -0
- package/dist/lcm/recall.d.ts +20 -0
- package/dist/lcm/recall.js +8 -0
- package/dist/lcm/recall.js.map +1 -0
- package/dist/lcm/schema.d.ts +16 -0
- package/dist/lcm/schema.js +14 -0
- package/dist/lcm/schema.js.map +1 -0
- package/dist/lcm/summarizer.d.ts +38 -0
- package/dist/lcm/summarizer.js +12 -0
- package/dist/lcm/summarizer.js.map +1 -0
- package/dist/lcm/tools.d.ts +29 -0
- package/dist/lcm/tools.js +8 -0
- package/dist/lcm/tools.js.map +1 -0
- package/dist/live-connectors-runner.js +5 -5
- package/dist/local-llm.js +3 -3
- package/dist/maintenance/archive-observations.d.ts +18 -0
- package/dist/maintenance/archive-observations.js +8 -0
- package/dist/maintenance/archive-observations.js.map +1 -0
- package/dist/maintenance/backup-stamp.d.ts +3 -0
- package/dist/maintenance/backup-stamp.js +8 -0
- package/dist/maintenance/backup-stamp.js.map +1 -0
- package/dist/maintenance/memory-governance-cron.d.ts +85 -0
- package/dist/maintenance/memory-governance-cron.js +22 -0
- package/dist/maintenance/memory-governance-cron.js.map +1 -0
- package/dist/maintenance/memory-governance.d.ts +137 -0
- package/dist/maintenance/memory-governance.js +40 -0
- package/dist/maintenance/memory-governance.js.map +1 -0
- package/dist/maintenance/migrate-observations.d.ts +18 -0
- package/dist/maintenance/migrate-observations.js +9 -0
- package/dist/maintenance/migrate-observations.js.map +1 -0
- package/dist/maintenance/observation-ledger-utils.d.ts +10 -0
- package/dist/maintenance/observation-ledger-utils.js +10 -0
- package/dist/maintenance/observation-ledger-utils.js.map +1 -0
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +15 -0
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +28 -0
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js.map +1 -0
- package/dist/maintenance/rebuild-memory-projection.d.ts +77 -0
- package/dist/maintenance/rebuild-memory-projection.js +35 -0
- package/dist/maintenance/rebuild-memory-projection.js.map +1 -0
- package/dist/maintenance/rebuild-observations.d.ts +17 -0
- package/dist/maintenance/rebuild-observations.js +9 -0
- package/dist/maintenance/rebuild-observations.js.map +1 -0
- package/dist/mcp-memory-inspector-app.d.ts +24 -6
- package/dist/memory-projection-store.d.ts +108 -3
- package/dist/memory-projection-store.js +2 -1
- package/dist/memory-worth-outcomes.d.ts +4 -2
- package/dist/migrate/from-engram.d.ts +24 -0
- package/dist/migrate/from-engram.js +12 -0
- package/dist/migrate/from-engram.js.map +1 -0
- package/dist/namespaces/migrate.d.ts +50 -0
- package/dist/namespaces/migrate.js +50 -0
- package/dist/namespaces/migrate.js.map +1 -0
- package/dist/namespaces/principal.d.ts +17 -0
- package/dist/namespaces/principal.js +16 -0
- package/dist/namespaces/principal.js.map +1 -0
- package/dist/namespaces/search.d.ts +46 -0
- package/dist/namespaces/search.js +28 -0
- package/dist/namespaces/search.js.map +1 -0
- package/dist/namespaces/storage.d.ts +32 -0
- package/dist/namespaces/storage.js +28 -0
- package/dist/namespaces/storage.js.map +1 -0
- package/dist/network/tailscale.d.ts +41 -0
- package/dist/network/tailscale.js +9 -0
- package/dist/network/tailscale.js.map +1 -0
- package/dist/network/webdav.d.ts +39 -0
- package/dist/network/webdav.js +10 -0
- package/dist/network/webdav.js.map +1 -0
- package/dist/objective-state-writers.js +2 -2
- package/dist/operator-toolkit.d.ts +4 -2
- package/dist/operator-toolkit.js +32 -14
- package/dist/opik-exporter.js +2 -2
- package/dist/opik-exporter.js.map +1 -1
- package/dist/orchestrator-DuWl9Hwx.d.ts +1244 -0
- package/dist/orchestrator.d.ts +22 -7
- package/dist/orchestrator.js +79 -44
- package/dist/path-MR5JPYOP.js +9 -0
- package/dist/path-MR5JPYOP.js.map +1 -0
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +102 -3
- package/dist/qmd.js +23 -5
- package/dist/recall-explain-renderer.js +3 -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/replay/normalizers/chatgpt.d.ts +6 -0
- package/dist/replay/normalizers/chatgpt.js +11 -0
- package/dist/replay/normalizers/chatgpt.js.map +1 -0
- package/dist/replay/normalizers/claude.d.ts +6 -0
- package/dist/replay/normalizers/claude.js +11 -0
- package/dist/replay/normalizers/claude.js.map +1 -0
- package/dist/replay/normalizers/openclaw.d.ts +6 -0
- package/dist/replay/normalizers/openclaw.js +11 -0
- package/dist/replay/normalizers/openclaw.js.map +1 -0
- package/dist/replay/normalizers/shared.d.ts +16 -0
- package/dist/replay/normalizers/shared.js +14 -0
- package/dist/replay/normalizers/shared.js.map +1 -0
- package/dist/replay/runner.d.ts +35 -0
- package/dist/replay/runner.js +16 -0
- package/dist/replay/runner.js.map +1 -0
- package/dist/replay/types.d.ts +57 -0
- package/dist/replay/types.js +19 -0
- package/dist/replay/types.js.map +1 -0
- package/dist/resolution-B7FNQSSP.js +12 -0
- package/dist/resolution-B7FNQSSP.js.map +1 -0
- package/dist/resolve-provider-secret.js +2 -2
- package/dist/resume-bundles.js +8 -6
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/routing/engine.d.ts +35 -0
- package/dist/routing/engine.js +16 -0
- package/dist/routing/engine.js.map +1 -0
- package/dist/routing/store.d.ts +27 -0
- package/dist/routing/store.js +10 -0
- package/dist/routing/store.js.map +1 -0
- package/dist/runtime/better-sqlite.d.ts +8 -0
- package/dist/runtime/better-sqlite.js +10 -0
- package/dist/runtime/better-sqlite.js.map +1 -0
- package/dist/runtime/child-process.d.ts +32 -0
- package/dist/runtime/child-process.js +10 -0
- package/dist/runtime/child-process.js.map +1 -0
- package/dist/runtime/env.d.ts +5 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/env.js.map +1 -0
- package/dist/schemas.d.ts +22 -22
- package/dist/sdk-compat.js +1 -1
- package/dist/search/document-scanner.d.ts +22 -0
- package/dist/search/document-scanner.js +8 -0
- package/dist/search/document-scanner.js.map +1 -0
- package/dist/search/embed-helper.d.ts +35 -0
- package/dist/search/embed-helper.js +9 -0
- package/dist/search/embed-helper.js.map +1 -0
- package/dist/search/factory.d.ts +32 -0
- package/dist/search/factory.js +29 -0
- package/dist/search/factory.js.map +1 -0
- package/dist/search/index.d.ts +15 -0
- package/dist/search/index.js +50 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/lancedb-backend.d.ts +51 -0
- package/dist/search/lancedb-backend.js +10 -0
- package/dist/search/lancedb-backend.js.map +1 -0
- package/dist/search/meilisearch-backend.d.ts +48 -0
- package/dist/search/meilisearch-backend.js +10 -0
- package/dist/search/meilisearch-backend.js.map +1 -0
- package/dist/search/noop-backend.d.ts +26 -0
- package/dist/search/noop-backend.js +8 -0
- package/dist/search/noop-backend.js.map +1 -0
- package/dist/search/orama-backend.d.ts +53 -0
- package/dist/search/orama-backend.js +10 -0
- package/dist/search/orama-backend.js.map +1 -0
- package/dist/search/port.d.ts +61 -0
- package/dist/search/port.js +1 -0
- package/dist/search/port.js.map +1 -0
- package/dist/search/remote-backend.d.ts +39 -0
- package/dist/search/remote-backend.js +9 -0
- package/dist/search/remote-backend.js.map +1 -0
- package/dist/secure-store/index.d.ts +890 -0
- package/dist/secure-store/index.js +156 -0
- package/dist/secure-store/index.js.map +1 -0
- package/dist/semantic-VwGI14Ok.d.ts +69 -0
- package/dist/semantic-consolidation-4HkHWgeI.d.ts +180 -0
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -6
- package/dist/semantic-rule-promotion.js +8 -5
- package/dist/semantic-rule-verifier.js +8 -5
- package/dist/shared-context/manager.d.ts +131 -0
- package/dist/shared-context/manager.js +15 -0
- package/dist/shared-context/manager.js.map +1 -0
- package/dist/skills-registry.js +13 -1
- package/dist/skills-registry.js.map +1 -1
- package/dist/state-store-VZU2IA53.js +16 -0
- package/dist/state-store-VZU2IA53.js.map +1 -0
- package/dist/storage-paths.d.ts +9 -0
- package/dist/storage-paths.js +20 -0
- package/dist/storage-paths.js.map +1 -0
- package/dist/storage.d.ts +3 -1
- package/dist/storage.js +7 -4
- package/dist/summarizer.d.ts +5 -0
- package/dist/summarizer.js +9 -8
- package/dist/summary-snapshot.js +2 -1
- package/dist/surfaces/dreams.d.ts +16 -0
- package/dist/surfaces/dreams.js +282 -0
- package/dist/surfaces/dreams.js.map +1 -0
- package/dist/surfaces/heartbeat.d.ts +17 -0
- package/dist/surfaces/heartbeat.js +265 -0
- package/dist/surfaces/heartbeat.js.map +1 -0
- package/dist/temporal-supersession.d.ts +3 -1
- package/dist/threading.d.ts +5 -0
- package/dist/threading.js +2 -1
- package/dist/tier-migration.d.ts +4 -2
- package/dist/tokens.js +2 -2
- package/dist/transcript.d.ts +15 -1
- package/dist/transcript.js +2 -1
- package/dist/transfer/autodetect.d.ts +4 -0
- package/dist/transfer/autodetect.js +15 -0
- package/dist/transfer/autodetect.js.map +1 -0
- package/dist/transfer/backup.d.ts +21 -0
- package/dist/transfer/backup.js +17 -0
- package/dist/transfer/backup.js.map +1 -0
- package/dist/transfer/capsule-export.d.ts +113 -0
- package/dist/transfer/capsule-export.js +19 -0
- package/dist/transfer/capsule-export.js.map +1 -0
- package/dist/transfer/capsule-import.d.ts +124 -0
- package/dist/transfer/capsule-import.js +16 -0
- package/dist/transfer/capsule-import.js.map +1 -0
- package/dist/transfer/constants.d.ts +13 -0
- package/dist/transfer/constants.js +12 -0
- package/dist/transfer/constants.js.map +1 -0
- package/dist/transfer/export-json.d.ts +11 -0
- package/dist/transfer/export-json.js +11 -0
- package/dist/transfer/export-json.js.map +1 -0
- package/dist/transfer/export-md.d.ts +10 -0
- package/dist/transfer/export-md.js +13 -0
- package/dist/transfer/export-md.js.map +1 -0
- package/dist/transfer/export-sqlite.d.ts +9 -0
- package/dist/transfer/export-sqlite.js +12 -0
- package/dist/transfer/export-sqlite.js.map +1 -0
- package/dist/transfer/fs-utils.d.ts +61 -0
- package/dist/transfer/fs-utils.js +40 -0
- package/dist/transfer/fs-utils.js.map +1 -0
- package/dist/transfer/import-json.d.ts +16 -0
- package/dist/transfer/import-json.js +13 -0
- package/dist/transfer/import-json.js.map +1 -0
- package/dist/transfer/import-md.d.ts +14 -0
- package/dist/transfer/import-md.js +11 -0
- package/dist/transfer/import-md.js.map +1 -0
- package/dist/transfer/import-sqlite.d.ts +14 -0
- package/dist/transfer/import-sqlite.js +12 -0
- package/dist/transfer/import-sqlite.js.map +1 -0
- package/dist/transfer/sqlite-schema.d.ts +4 -0
- package/dist/transfer/sqlite-schema.js +10 -0
- package/dist/transfer/sqlite-schema.js.map +1 -0
- package/dist/transfer/types.d.ts +916 -0
- package/dist/transfer/types.js +30 -0
- package/dist/transfer/types.js.map +1 -0
- package/dist/types.d.ts +28 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +9 -6
- package/dist/work/board.d.ts +43 -0
- package/dist/work/board.js +14 -0
- package/dist/work/board.js.map +1 -0
- package/dist/work/boundary.d.ts +8 -0
- package/dist/work/boundary.js +14 -0
- package/dist/work/boundary.js.map +1 -0
- package/dist/work/storage.d.ts +39 -0
- package/dist/work/storage.js +11 -0
- package/dist/work/storage.js.map +1 -0
- package/dist/work/types.d.ts +75 -0
- package/dist/work/types.js +1 -0
- package/dist/work/types.js.map +1 -0
- package/package.json +2767 -6
- package/scripts/faiss_index.py +816 -0
- package/scripts/faiss_requirements.txt +3 -0
- package/skills/remnic-entities/SKILL.md +51 -0
- package/skills/remnic-memory-workflow/SKILL.md +61 -0
- package/skills/remnic-recall/SKILL.md +51 -0
- package/skills/remnic-remember/SKILL.md +56 -0
- package/skills/remnic-search/SKILL.md +51 -0
- package/skills/remnic-status/SKILL.md +51 -0
- package/src/abort-error.test.ts +49 -0
- package/src/abort-error.ts +46 -0
- package/src/abstraction-nodes.ts +162 -0
- package/src/access-audit.test.ts +178 -0
- package/src/access-audit.ts +125 -0
- package/src/access-cli.test.ts +439 -0
- package/src/access-cli.ts +438 -0
- package/src/access-http.test.ts +225 -0
- package/src/access-http.ts +1899 -0
- package/src/access-idempotency.ts +232 -0
- package/src/access-mcp.test.ts +568 -0
- package/src/access-mcp.ts +3056 -0
- package/src/access-schema-pi.test.ts +60 -0
- package/src/access-schema.ts +522 -0
- package/src/access-service-namespace.test.ts +123 -0
- package/src/access-service.ts +5629 -0
- package/src/action-confidence.test.ts +206 -0
- package/src/action-confidence.ts +466 -0
- package/src/active-memory-bridge.test.ts +285 -0
- package/src/active-memory-bridge.ts +217 -0
- package/src/active-recall.test.ts +484 -0
- package/src/active-recall.ts +459 -0
- package/src/adapters/claude-code.ts +56 -0
- package/src/adapters/codex.ts +57 -0
- package/src/adapters/hermes.ts +64 -0
- package/src/adapters/index.ts +6 -0
- package/src/adapters/registry.ts +41 -0
- package/src/adapters/replit.ts +55 -0
- package/src/adapters/types.ts +51 -0
- package/src/behavior-learner.ts +144 -0
- package/src/behavior-signals.ts +73 -0
- package/src/binary-lifecycle/backend.ts +117 -0
- package/src/binary-lifecycle/index.ts +35 -0
- package/src/binary-lifecycle/manifest.ts +79 -0
- package/src/binary-lifecycle/pipeline.ts +352 -0
- package/src/binary-lifecycle/scanner.ts +89 -0
- package/src/binary-lifecycle/types.ts +89 -0
- package/src/bootstrap.ts +178 -0
- package/src/boxes.ts +521 -0
- package/src/briefing.test.ts +1535 -0
- package/src/briefing.ts +1382 -0
- package/src/buffer-session.test.ts +443 -0
- package/src/buffer-surprise-report.ts +176 -0
- package/src/buffer-surprise-telemetry.test.ts +606 -0
- package/src/buffer-surprise-trigger.test.ts +766 -0
- package/src/buffer-surprise.test.ts +339 -0
- package/src/buffer-surprise.ts +203 -0
- package/src/buffer.ts +900 -0
- package/src/bulk-import/cli-command.test.ts +204 -0
- package/src/bulk-import/index.ts +34 -0
- package/src/bulk-import/pipeline.test.ts +445 -0
- package/src/bulk-import/pipeline.ts +178 -0
- package/src/bulk-import/registry.test.ts +151 -0
- package/src/bulk-import/registry.ts +72 -0
- package/src/bulk-import/types.test.ts +272 -0
- package/src/bulk-import/types.ts +145 -0
- package/src/calibration.ts +394 -0
- package/src/capsule-cli.test.ts +398 -0
- package/src/capsule-cli.ts +565 -0
- package/src/causal-behavior.ts +308 -0
- package/src/causal-chain.ts +419 -0
- package/src/causal-consolidation.ts +370 -0
- package/src/causal-retrieval.ts +286 -0
- package/src/causal-trajectory-graph.ts +60 -0
- package/src/causal-trajectory.ts +303 -0
- package/src/chunking.ts +220 -0
- package/src/citations.ts +232 -0
- package/src/cli.ts +9403 -0
- package/src/codex-cli-fallback.ts +162 -0
- package/src/codex-thread-key.ts +1 -0
- package/src/coding/access-coding-context.test.ts +197 -0
- package/src/coding/coding-branch-scope.test.ts +281 -0
- package/src/coding/coding-namespace.test.ts +360 -0
- package/src/coding/coding-namespace.ts +412 -0
- package/src/coding/coding-orchestrator.test.ts +249 -0
- package/src/coding/git-context.test.ts +507 -0
- package/src/coding/git-context.ts +336 -0
- package/src/coding/mcp-set-coding-context.test.ts +174 -0
- package/src/coding/review-context.test.ts +316 -0
- package/src/coding/review-context.ts +349 -0
- package/src/coding/wire-coding-context.test.ts +468 -0
- package/src/commitment-ledger.test.ts +78 -0
- package/src/commitment-ledger.ts +337 -0
- package/src/compat/checks.test.ts +206 -0
- package/src/compat/checks.ts +716 -0
- package/src/compat/types.ts +33 -0
- package/src/compounding/engine.ts +1686 -0
- package/src/compounding/preference-consolidator.ts +778 -0
- package/src/compression-optimizer.ts +312 -0
- package/src/config.test.ts +930 -0
- package/src/config.ts +3807 -0
- package/src/connectors/codex/instructions.md +160 -0
- package/src/connectors/codex/resources/namespace-cheatsheet.md +48 -0
- package/src/connectors/codex-marketplace.ts +500 -0
- package/src/connectors/codex-materialize-runner.ts +212 -0
- package/src/connectors/codex-materialize.ts +983 -0
- package/src/connectors/coerce.ts +62 -0
- package/src/connectors/index.test.ts +1570 -0
- package/src/connectors/index.ts +3222 -0
- package/src/connectors/live/framework.ts +164 -0
- package/src/connectors/live/github.test.ts +1218 -0
- package/src/connectors/live/github.ts +1068 -0
- package/src/connectors/live/gmail.test.ts +1706 -0
- package/src/connectors/live/gmail.ts +1293 -0
- package/src/connectors/live/google-drive.test.ts +696 -0
- package/src/connectors/live/google-drive.ts +724 -0
- package/src/connectors/live/index.ts +101 -0
- package/src/connectors/live/live-connectors.test.ts +689 -0
- package/src/connectors/live/notion.test.ts +1109 -0
- package/src/connectors/live/notion.ts +978 -0
- package/src/connectors/live/registry.ts +103 -0
- package/src/connectors/live/state-store.ts +399 -0
- package/src/connectors/live/transient-errors.ts +150 -0
- package/src/connectors/weclone-installer.test.ts +850 -0
- package/src/connectors-cli.ts +513 -0
- package/src/console/state.test.ts +224 -0
- package/src/console/state.ts +514 -0
- package/src/console/trace.test.ts +813 -0
- package/src/console/trace.ts +603 -0
- package/src/console/tui.test.ts +582 -0
- package/src/console/tui.ts +508 -0
- package/src/consolidation-operator.ts +182 -0
- package/src/consolidation-provenance-check.ts +551 -0
- package/src/consolidation-undo.ts +718 -0
- package/src/contradiction/contradiction-judge.test.ts +189 -0
- package/src/contradiction/contradiction-judge.ts +333 -0
- package/src/contradiction/contradiction-review.ts +574 -0
- package/src/contradiction/contradiction-scan.ts +504 -0
- package/src/contradiction/contradiction.test.ts +2230 -0
- package/src/contradiction/index.ts +37 -0
- package/src/contradiction/resolution.ts +383 -0
- package/src/conversation-index/backend.ts +323 -0
- package/src/conversation-index/chunker.ts +47 -0
- package/src/conversation-index/cleanup.ts +53 -0
- package/src/conversation-index/faiss-adapter.ts +384 -0
- package/src/conversation-index/indexer.test.ts +164 -0
- package/src/conversation-index/indexer.ts +192 -0
- package/src/conversation-index/search.ts +37 -0
- package/src/cross-namespace-budget.test.ts +275 -0
- package/src/cross-namespace-budget.ts +365 -0
- package/src/cue-anchors.ts +163 -0
- package/src/curation/index.ts +544 -0
- package/src/dashboard-runtime.ts +337 -0
- package/src/day-summary.ts +122 -0
- package/src/dedup/index.ts +330 -0
- package/src/dedup/semantic.test.ts +1577 -0
- package/src/dedup/semantic.ts +148 -0
- package/src/delinearize.ts +193 -0
- package/src/direct-answer-wiring.test.ts +473 -0
- package/src/direct-answer-wiring.ts +180 -0
- package/src/direct-answer.test.ts +484 -0
- package/src/direct-answer.ts +273 -0
- package/src/embedding-fallback.ts +565 -0
- package/src/enrichment/audit.ts +89 -0
- package/src/enrichment/index.ts +27 -0
- package/src/enrichment/pipeline.ts +197 -0
- package/src/enrichment/provider-registry.ts +85 -0
- package/src/enrichment/types.ts +100 -0
- package/src/enrichment/web-search-provider.ts +63 -0
- package/src/entity-retrieval.ts +774 -0
- package/src/entity-schema.ts +239 -0
- package/src/evals.ts +1312 -0
- package/src/event-order-recall.test.ts +4164 -0
- package/src/event-order-recall.ts +2802 -0
- package/src/evidence-pack.test.ts +89 -0
- package/src/evidence-pack.ts +388 -0
- package/src/explicit-capture.ts +530 -0
- package/src/explicit-cue-recall.test.ts +3019 -0
- package/src/explicit-cue-recall.ts +5545 -0
- package/src/extraction-judge-telemetry.ts +234 -0
- package/src/extraction-judge-training.ts +221 -0
- package/src/extraction-judge.ts +846 -0
- package/src/extraction-timeout.test.ts +265 -0
- package/src/extraction.ts +2719 -0
- package/src/fallback-llm.test.ts +1060 -0
- package/src/fallback-llm.ts +918 -0
- package/src/focused-list-recall.test.ts +734 -0
- package/src/focused-list-recall.ts +1160 -0
- package/src/graph-dashboard-diff.ts +35 -0
- package/src/graph-dashboard-key.ts +5 -0
- package/src/graph-dashboard-parser.ts +104 -0
- package/src/graph-edge-reinforcement.ts +192 -0
- package/src/graph-events.ts +151 -0
- package/src/graph-recall.test.ts +164 -0
- package/src/graph-recall.ts +189 -0
- package/src/graph-retrieval.test.ts +809 -0
- package/src/graph-retrieval.ts +823 -0
- package/src/graph-snapshot.ts +329 -0
- package/src/graph.ts +813 -0
- package/src/harmonic-retrieval.ts +223 -0
- package/src/himem.ts +154 -0
- package/src/hygiene.ts +87 -0
- package/src/identity-continuity.ts +333 -0
- package/src/importance.ts +328 -0
- package/src/importers/base.test.ts +294 -0
- package/src/importers/base.ts +436 -0
- package/src/importers/index.ts +21 -0
- package/src/index.ts +1204 -0
- package/src/intent.ts +154 -0
- package/src/json-extract.ts +85 -0
- package/src/json-store.ts +42 -0
- package/src/lcm/archive.ts +617 -0
- package/src/lcm/dag.ts +199 -0
- package/src/lcm/engine.ts +645 -0
- package/src/lcm/index.ts +7 -0
- package/src/lcm/queue.test.ts +178 -0
- package/src/lcm/queue.ts +200 -0
- package/src/lcm/recall.ts +117 -0
- package/src/lcm/schema.ts +154 -0
- package/src/lcm/summarizer.ts +235 -0
- package/src/lcm/tools.ts +191 -0
- package/src/lcm-engine.test.ts +660 -0
- package/src/legacy-hook-compat.test.ts +20 -0
- package/src/legacy-hook-compat.ts +45 -0
- package/src/lifecycle.ts +289 -0
- package/src/live-connectors-runner.ts +385 -0
- package/src/local-llm-qos.test.ts +303 -0
- package/src/local-llm-thinking.test.ts +292 -0
- package/src/local-llm.ts +1464 -0
- package/src/logger.ts +49 -0
- package/src/maintenance/archive-observations.ts +147 -0
- package/src/maintenance/backup-stamp.ts +3 -0
- package/src/maintenance/dreams-ledger.ts +516 -0
- package/src/maintenance/first-start-migration.ts +362 -0
- package/src/maintenance/forget.test.ts +206 -0
- package/src/maintenance/forget.ts +126 -0
- package/src/maintenance/graph-edge-decay.test.ts +409 -0
- package/src/maintenance/graph-edge-decay.ts +394 -0
- package/src/maintenance/memory-governance-cron.ts +447 -0
- package/src/maintenance/memory-governance.ts +1039 -0
- package/src/maintenance/migrate-observations.ts +216 -0
- package/src/maintenance/observation-ledger-utils.ts +54 -0
- package/src/maintenance/pattern-reinforcement.test.ts +875 -0
- package/src/maintenance/pattern-reinforcement.ts +369 -0
- package/src/maintenance/purge.ts +334 -0
- package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +78 -0
- package/src/maintenance/rebuild-memory-projection.ts +1234 -0
- package/src/maintenance/rebuild-observations.ts +178 -0
- package/src/maintenance/tier-stats.test.ts +378 -0
- package/src/maintenance/tier-stats.ts +222 -0
- package/src/mcp-memory-inspector-app.ts +421 -0
- package/src/memory-action-policy.ts +80 -0
- package/src/memory-cache.ts +208 -0
- package/src/memory-extension/claude-code-publisher.ts +51 -0
- package/src/memory-extension/codex-publisher.ts +149 -0
- package/src/memory-extension/hermes-publisher.ts +51 -0
- package/src/memory-extension/index.ts +100 -0
- package/src/memory-extension/shared-instructions.ts +133 -0
- package/src/memory-extension/types.ts +86 -0
- package/src/memory-extension-host/host-discovery.ts +276 -0
- package/src/memory-extension-host/index.ts +14 -0
- package/src/memory-extension-host/render-extensions-block.ts +73 -0
- package/src/memory-extension-host/types.ts +21 -0
- package/src/memory-lifecycle-ledger-utils.ts +116 -0
- package/src/memory-projection-format.ts +11 -0
- package/src/memory-projection-store.ts +951 -0
- package/src/memory-provenance.test.ts +196 -0
- package/src/memory-provenance.ts +484 -0
- package/src/memory-worth-bench.test.ts +71 -0
- package/src/memory-worth-bench.ts +265 -0
- package/src/memory-worth-filter.test.ts +209 -0
- package/src/memory-worth-filter.ts +204 -0
- package/src/memory-worth-frontmatter.test.ts +311 -0
- package/src/memory-worth-outcomes.test.ts +316 -0
- package/src/memory-worth-outcomes.ts +286 -0
- package/src/memory-worth.test.ts +317 -0
- package/src/memory-worth.ts +215 -0
- package/src/message-parts/index.ts +806 -0
- package/src/message-parts/message-parts.test.ts +421 -0
- package/src/migrate/from-engram.ts +789 -0
- package/src/model-registry.ts +313 -0
- package/src/models-json.ts +76 -0
- package/src/namespaces/migrate.ts +187 -0
- package/src/namespaces/path.ts +25 -0
- package/src/namespaces/principal.test.ts +195 -0
- package/src/namespaces/principal.ts +86 -0
- package/src/namespaces/search.test.ts +105 -0
- package/src/namespaces/search.ts +233 -0
- package/src/namespaces/storage.ts +74 -0
- package/src/native-knowledge.ts +1823 -0
- package/src/negative.ts +72 -0
- package/src/network/tailscale.ts +179 -0
- package/src/network/webdav.ts +385 -0
- package/src/objective-state-writers.ts +951 -0
- package/src/objective-state.ts +320 -0
- package/src/onboarding/index.ts +529 -0
- package/src/openai-chat-compat.ts +56 -0
- package/src/operator-toolkit.ts +2132 -0
- package/src/opik-exporter.test.ts +72 -0
- package/src/opik-exporter.ts +587 -0
- package/src/orchestrator-extraction-queue.test.ts +197 -0
- package/src/orchestrator-flush.test.ts +1171 -0
- package/src/orchestrator-pattern-reinforcement.test.ts +128 -0
- package/src/orchestrator-source-attribution.test.ts +701 -0
- package/src/orchestrator.ts +16368 -0
- package/src/page-versioning.ts +450 -0
- package/src/patterns-cli.ts +574 -0
- package/src/peers/index.ts +54 -0
- package/src/peers/migrate-from-identity-anchor.test.ts +291 -0
- package/src/peers/migrate-from-identity-anchor.ts +350 -0
- package/src/peers/peers.test.ts +419 -0
- package/src/peers/profile-reasoner.ts +694 -0
- package/src/peers/storage.ts +1350 -0
- package/src/peers/types.ts +138 -0
- package/src/plugin-id.ts +84 -0
- package/src/policy-runtime.ts +209 -0
- package/src/procedural/procedure-miner.ts +150 -0
- package/src/procedural/procedure-recall.ts +93 -0
- package/src/procedural/procedure-stats.ts +213 -0
- package/src/procedural/procedure-types.ts +132 -0
- package/src/procedural/reinforcement-core.test.ts +132 -0
- package/src/procedural/reinforcement-core.ts +73 -0
- package/src/profiling.test.ts +263 -0
- package/src/profiling.ts +435 -0
- package/src/projection/index.ts +398 -0
- package/src/qmd-recall-cache.test.ts +138 -0
- package/src/qmd-recall-cache.ts +111 -0
- package/src/qmd.test.ts +257 -0
- package/src/qmd.ts +2614 -0
- package/src/reasoning-trace-recall.ts +201 -0
- package/src/reasoning-trace-types.ts +235 -0
- package/src/recall-audit-anomaly.test.ts +246 -0
- package/src/recall-audit-anomaly.ts +297 -0
- package/src/recall-audit.test.ts +51 -0
- package/src/recall-audit.ts +72 -0
- package/src/recall-budget-config.test.ts +87 -0
- package/src/recall-disclosure-escalation.test.ts +196 -0
- package/src/recall-disclosure-escalation.ts +158 -0
- package/src/recall-disclosure-shaping.test.ts +146 -0
- package/src/recall-disclosure.test.ts +214 -0
- package/src/recall-explain-renderer.test.ts +140 -0
- package/src/recall-explain-renderer.ts +356 -0
- package/src/recall-mmr.test.ts +808 -0
- package/src/recall-mmr.ts +607 -0
- package/src/recall-qos.test.ts +85 -0
- package/src/recall-qos.ts +82 -0
- package/src/recall-query-policy.ts +221 -0
- package/src/recall-state.test.ts +233 -0
- package/src/recall-state.ts +456 -0
- package/src/recall-tag-filter.ts +143 -0
- package/src/recall-tokenization.ts +35 -0
- package/src/recall-xray-cli.test.ts +118 -0
- package/src/recall-xray-cli.ts +100 -0
- package/src/recall-xray-disclosure-telemetry.test.ts +183 -0
- package/src/recall-xray-renderer.test.ts +539 -0
- package/src/recall-xray-renderer.ts +487 -0
- package/src/recall-xray.test.ts +503 -0
- package/src/recall-xray.ts +621 -0
- package/src/reconstruct.ts +41 -0
- package/src/release-changelog.ts +35 -0
- package/src/relevance.ts +67 -0
- package/src/replay/normalizers/chatgpt.ts +133 -0
- package/src/replay/normalizers/claude.ts +102 -0
- package/src/replay/normalizers/openclaw.ts +119 -0
- package/src/replay/normalizers/shared.ts +69 -0
- package/src/replay/runner.ts +197 -0
- package/src/replay/types.ts +143 -0
- package/src/rerank.test.ts +48 -0
- package/src/rerank.ts +176 -0
- package/src/resolve-auth-token.test.ts +226 -0
- package/src/resolve-auth-token.ts +151 -0
- package/src/resolve-provider-secret.test.ts +187 -0
- package/src/resolve-provider-secret.ts +410 -0
- package/src/response-guidance-recall.test.ts +3952 -0
- package/src/response-guidance-recall.ts +4431 -0
- package/src/resume-bundles.ts +415 -0
- package/src/retrieval-agents.ts +623 -0
- package/src/retrieval-tiers.ts +25 -0
- package/src/retrieval.ts +104 -0
- package/src/review/index.test.ts +201 -0
- package/src/review/index.ts +536 -0
- package/src/routing/engine.ts +162 -0
- package/src/routing/store.ts +321 -0
- package/src/runtime/better-sqlite.test.ts +32 -0
- package/src/runtime/better-sqlite.ts +76 -0
- package/src/runtime/child-process.ts +67 -0
- package/src/runtime/env.ts +48 -0
- package/src/sanitize.ts +58 -0
- package/src/schemas.ts +449 -0
- package/src/sdk-compat.ts +87 -0
- package/src/search/document-scanner.ts +96 -0
- package/src/search/embed-helper.ts +142 -0
- package/src/search/factory.ts +189 -0
- package/src/search/index.ts +10 -0
- package/src/search/lancedb-backend.ts +342 -0
- package/src/search/meilisearch-backend.ts +232 -0
- package/src/search/noop-backend.ts +57 -0
- package/src/search/orama-backend.ts +358 -0
- package/src/search/port.ts +86 -0
- package/src/search/remote-backend.ts +124 -0
- package/src/secure-store/cipher.ts +271 -0
- package/src/secure-store/cli-handlers.ts +355 -0
- package/src/secure-store/cli-renderer.ts +131 -0
- package/src/secure-store/header.ts +373 -0
- package/src/secure-store/index.ts +137 -0
- package/src/secure-store/kdf.ts +263 -0
- package/src/secure-store/keyring.ts +106 -0
- package/src/secure-store/metadata.ts +394 -0
- package/src/secure-store/passphrase-reader.ts +252 -0
- package/src/secure-store/secure-fs.ts +571 -0
- package/src/secure-store/secure-store.test.ts +755 -0
- package/src/semantic-chunking.ts +545 -0
- package/src/semantic-consolidation.test.ts +182 -0
- package/src/semantic-consolidation.ts +432 -0
- package/src/semantic-rule-promotion.ts +183 -0
- package/src/semantic-rule-verifier.ts +160 -0
- package/src/session-integrity.ts +569 -0
- package/src/session-observer-bands.ts +11 -0
- package/src/session-observer-state.ts +346 -0
- package/src/session-toggles.test.ts +96 -0
- package/src/session-toggles.ts +159 -0
- package/src/shared-context/manager.ts +810 -0
- package/src/signal.ts +84 -0
- package/src/skills-registry.test.ts +277 -0
- package/src/skills-registry.ts +120 -0
- package/src/source-attribution-roundtrip.test.ts +215 -0
- package/src/source-attribution.test.ts +1425 -0
- package/src/source-attribution.ts +639 -0
- package/src/spaces/index.ts +627 -0
- package/src/storage-paths.ts +117 -0
- package/src/storage.ts +6657 -0
- package/src/store-contract.ts +55 -0
- package/src/summarizer.ts +844 -0
- package/src/summary-snapshot.test.ts +681 -0
- package/src/summary-snapshot.ts +238 -0
- package/src/surfaces/dreams.test.ts +394 -0
- package/src/surfaces/dreams.ts +346 -0
- package/src/surfaces/heartbeat.test.ts +415 -0
- package/src/surfaces/heartbeat.ts +325 -0
- package/src/sync/index.ts +308 -0
- package/src/targeted-fact-recall.test.ts +1694 -0
- package/src/targeted-fact-recall.ts +2905 -0
- package/src/taxonomy/default-taxonomy.ts +87 -0
- package/src/taxonomy/index.ts +26 -0
- package/src/taxonomy/resolver-doc-generator.ts +57 -0
- package/src/taxonomy/resolver.ts +184 -0
- package/src/taxonomy/taxonomy-loader.ts +186 -0
- package/src/taxonomy/types.ts +48 -0
- package/src/telemetry-transcript.ts +70 -0
- package/src/temporal-index.ts +890 -0
- package/src/temporal-supersession.test.ts +2703 -0
- package/src/temporal-supersession.ts +493 -0
- package/src/temporal-validity.test.ts +448 -0
- package/src/temporal-validity.ts +123 -0
- package/src/threading.ts +395 -0
- package/src/tier-migration.ts +124 -0
- package/src/tier-routing.ts +102 -0
- package/src/tmt.ts +462 -0
- package/src/tokens.test.ts +178 -0
- package/src/tokens.ts +279 -0
- package/src/topics.ts +147 -0
- package/src/training-export/cli-date-validation.test.ts +258 -0
- package/src/training-export/converter.test.ts +452 -0
- package/src/training-export/converter.ts +319 -0
- package/src/training-export/date-parse.ts +117 -0
- package/src/training-export/index.ts +26 -0
- package/src/training-export/registry.test.ts +85 -0
- package/src/training-export/registry.ts +57 -0
- package/src/training-export/types.ts +31 -0
- package/src/transcript.ts +1179 -0
- package/src/transfer/autodetect.ts +30 -0
- package/src/transfer/backup.ts +138 -0
- package/src/transfer/capsule-crypto.ts +485 -0
- package/src/transfer/capsule-encrypt.test.ts +690 -0
- package/src/transfer/capsule-export.ts +543 -0
- package/src/transfer/capsule-fork.ts +375 -0
- package/src/transfer/capsule-import.ts +564 -0
- package/src/transfer/capsule-merge.ts +433 -0
- package/src/transfer/conflict-policy.ts +16 -0
- package/src/transfer/constants.ts +13 -0
- package/src/transfer/exclusions.ts +37 -0
- package/src/transfer/export-json.ts +65 -0
- package/src/transfer/export-md.ts +59 -0
- package/src/transfer/export-sqlite.ts +52 -0
- package/src/transfer/fs-utils.ts +269 -0
- package/src/transfer/import-json.ts +108 -0
- package/src/transfer/import-md.ts +84 -0
- package/src/transfer/import-sqlite.ts +100 -0
- package/src/transfer/integrity.ts +71 -0
- package/src/transfer/sqlite-schema.ts +16 -0
- package/src/transfer/types.ts +297 -0
- package/src/trust-zones.ts +1186 -0
- package/src/types.ts +3074 -0
- package/src/user-model.test.ts +124 -0
- package/src/user-model.ts +162 -0
- package/src/utility-learner.ts +353 -0
- package/src/utility-runtime.ts +88 -0
- package/src/utility-telemetry.ts +215 -0
- package/src/utils/category-dir.ts +44 -0
- package/src/utils/errno.ts +6 -0
- package/src/utils/iso-timestamp.test.ts +37 -0
- package/src/utils/iso-timestamp.ts +164 -0
- package/src/utils/path.ts +26 -0
- package/src/verified-recall.ts +138 -0
- package/src/version-utils.test.ts +10 -0
- package/src/version-utils.ts +9 -0
- package/src/whitespace.ts +10 -0
- package/src/work/board.ts +359 -0
- package/src/work/boundary.ts +107 -0
- package/src/work/storage.ts +436 -0
- package/src/work/types.ts +82 -0
- package/src/work-product-ledger.ts +265 -0
- package/dist/access-service-DDjzFALq.d.ts +0 -2088
- package/dist/capsule-crypto-SJS5VVAP.js +0 -18
- package/dist/capsule-export-7QNCBZOQ.js +0 -17
- package/dist/capsule-import-EPBHD2EN.js +0 -16
- package/dist/capsule-merge-DI7PNQ2H.js +0 -189
- package/dist/chunk-23ZZK64Y.js +0 -26
- package/dist/chunk-23ZZK64Y.js.map +0 -1
- package/dist/chunk-242S3I2A.js +0 -647
- package/dist/chunk-2LGMW3DJ.js +0 -111
- package/dist/chunk-3B6KIRBH.js +0 -5213
- package/dist/chunk-3B6KIRBH.js.map +0 -1
- package/dist/chunk-457A4P3L.js +0 -119
- package/dist/chunk-457A4P3L.js.map +0 -1
- package/dist/chunk-4IS4SXIQ.js +0 -2040
- package/dist/chunk-4YM32CRU.js +0 -721
- package/dist/chunk-6TBWYBJ3.js +0 -236
- package/dist/chunk-74EMIVE4.js +0 -329
- package/dist/chunk-74EMIVE4.js.map +0 -1
- package/dist/chunk-767ODGE6.js +0 -183
- package/dist/chunk-7V22HTMD.js +0 -623
- package/dist/chunk-7V22HTMD.js.map +0 -1
- package/dist/chunk-7ZM3BFKK.js +0 -9705
- package/dist/chunk-7ZM3BFKK.js.map +0 -1
- package/dist/chunk-AQJNPMOA.js +0 -643
- package/dist/chunk-AQJNPMOA.js.map +0 -1
- package/dist/chunk-ASAITVLA.js +0 -64
- package/dist/chunk-ASAITVLA.js.map +0 -1
- package/dist/chunk-BBE34QBJ.js +0 -275
- package/dist/chunk-BBE34QBJ.js.map +0 -1
- package/dist/chunk-BZSQEPRW.js +0 -14710
- package/dist/chunk-BZSQEPRW.js.map +0 -1
- package/dist/chunk-CPKTBRS2.js +0 -891
- package/dist/chunk-CPKTBRS2.js.map +0 -1
- package/dist/chunk-D4GAOFF6.js +0 -562
- package/dist/chunk-D4GAOFF6.js.map +0 -1
- package/dist/chunk-D54LZC5L.js +0 -147
- package/dist/chunk-DF3RVK3X.js +0 -119
- package/dist/chunk-DF3RVK3X.js.map +0 -1
- package/dist/chunk-DZZPC36E.js +0 -1451
- package/dist/chunk-DZZPC36E.js.map +0 -1
- package/dist/chunk-E2UCDP5S.js +0 -570
- package/dist/chunk-E6K4NIEU.js +0 -747
- package/dist/chunk-E6K4NIEU.js.map +0 -1
- package/dist/chunk-EEQLFRUM.js +0 -89
- package/dist/chunk-ETOW6ACV.js +0 -158
- package/dist/chunk-ETOW6ACV.js.map +0 -1
- package/dist/chunk-FMEBPEAO.js +0 -347
- package/dist/chunk-FMEBPEAO.js.map +0 -1
- package/dist/chunk-FQDPCE3I.js +0 -1837
- package/dist/chunk-FQDPCE3I.js.map +0 -1
- package/dist/chunk-FYIYMQ5N.js +0 -221
- package/dist/chunk-FYIYMQ5N.js.map +0 -1
- package/dist/chunk-G2WADRQ3.js +0 -219
- package/dist/chunk-G4SK7DSQ.js +0 -121
- package/dist/chunk-GVPWB7EY.js +0 -390
- package/dist/chunk-GVPWB7EY.js.map +0 -1
- package/dist/chunk-HELQZFZO.js +0 -1075
- package/dist/chunk-HL5LRPNA.js +0 -1914
- package/dist/chunk-HL5LRPNA.js.map +0 -1
- package/dist/chunk-HQZVVSVB.js +0 -147
- package/dist/chunk-HQZVVSVB.js.map +0 -1
- package/dist/chunk-HY3L4WKC.js +0 -2195
- package/dist/chunk-HY3L4WKC.js.map +0 -1
- package/dist/chunk-IB3BFHGN.js +0 -228
- package/dist/chunk-IXEJRKCZ.js +0 -18
- package/dist/chunk-JBMSGZEQ.js +0 -441
- package/dist/chunk-JBMSGZEQ.js.map +0 -1
- package/dist/chunk-JESOB2HO.js +0 -108
- package/dist/chunk-JKDVIE52.js +0 -272
- package/dist/chunk-JRNQ3RNA.js +0 -284
- package/dist/chunk-JRNQ3RNA.js.map +0 -1
- package/dist/chunk-K6WK37A6.js +0 -865
- package/dist/chunk-K6WK37A6.js.map +0 -1
- package/dist/chunk-MARWOCVP.js +0 -48
- package/dist/chunk-MNU6ZBWT.js +0 -4454
- package/dist/chunk-MNU6ZBWT.js.map +0 -1
- package/dist/chunk-N5AKDXAI.js +0 -74
- package/dist/chunk-OA3L7BFR.js +0 -183
- package/dist/chunk-OA3L7BFR.js.map +0 -1
- package/dist/chunk-OR64ZGRZ.js +0 -23
- package/dist/chunk-P77UEOU2.js +0 -1521
- package/dist/chunk-P77UEOU2.js.map +0 -1
- package/dist/chunk-PH4C2U43.js +0 -239
- package/dist/chunk-PH4C2U43.js.map +0 -1
- package/dist/chunk-RVPLBATS.js +0 -1586
- package/dist/chunk-RVPLBATS.js.map +0 -1
- package/dist/chunk-U5JMRGKX.js +0 -340
- package/dist/chunk-U5JMRGKX.js.map +0 -1
- package/dist/chunk-URB2WSKZ.js +0 -350
- package/dist/chunk-URB2WSKZ.js.map +0 -1
- package/dist/chunk-UVMUAWVT.js +0 -596
- package/dist/chunk-WEJG4TB5.js +0 -118
- package/dist/chunk-X7HPGUVG.js +0 -271
- package/dist/chunk-XAMBKFQS.js +0 -2777
- package/dist/chunk-XAMBKFQS.js.map +0 -1
- package/dist/chunk-XJKFSSDW.js +0 -726
- package/dist/chunk-XJKFSSDW.js.map +0 -1
- package/dist/chunk-XMHBH5H6.js +0 -283
- package/dist/chunk-XMHBH5H6.js.map +0 -1
- package/dist/chunk-XMVFHBHT.js +0 -277
- package/dist/chunk-Y3VMVTYX.js +0 -53
- package/dist/chunk-YNB73F22.js +0 -137
- package/dist/chunk-YNB73F22.js.map +0 -1
- package/dist/chunk-Z2E7VW55.js +0 -335
- package/dist/chunk-Z2E7VW55.js.map +0 -1
- package/dist/chunk-ZG7PTKBK.js +0 -2296
- package/dist/chunk-ZNQN6ZTA.js +0 -135
- package/dist/chunk-ZVTKDVVM.js +0 -827
- package/dist/chunk-ZVTKDVVM.js.map +0 -1
- package/dist/cli-BR8KpIU0.d.ts +0 -1259
- package/dist/codex-materialize-CQlLTzke.d.ts +0 -139
- package/dist/connectors-cli-DFGtY2DB.d.ts +0 -257
- package/dist/contradiction-review-5LTTVDQV.js +0 -22
- package/dist/contradiction-scan-QTXAMBUA.js +0 -414
- package/dist/contradiction-scan-QTXAMBUA.js.map +0 -1
- package/dist/engine-35M5BKQ7.js +0 -28
- package/dist/fs-utils-IRVUFB6G.js +0 -30
- package/dist/graph-edge-decay-PWB63GRE.js +0 -207
- package/dist/memory-governance-IMPQZXFC.js +0 -37
- package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
- package/dist/orchestrator-DDMPqU6R.d.ts +0 -1792
- package/dist/path-RMTY5Y5A.js +0 -9
- package/dist/port-B6VEDIkC.d.ts +0 -53
- package/dist/resolution-YGIBORXI.js +0 -101
- package/dist/resolution-YGIBORXI.js.map +0 -1
- package/dist/secure-store-4R2GSO7S.js +0 -156
- package/dist/semantic-consolidation-ByBXb-sf.d.ts +0 -180
- package/dist/state-store-3EH7HYIN.js +0 -16
- package/dist/types-V3FJ26TF.js +0 -30
- /package/dist/{capsule-crypto-SJS5VVAP.js.map → adapters/claude-code.js.map} +0 -0
- /package/dist/{capsule-export-7QNCBZOQ.js.map → adapters/codex.js.map} +0 -0
- /package/dist/{capsule-import-EPBHD2EN.js.map → adapters/hermes.js.map} +0 -0
- /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/index.js.map} +0 -0
- /package/dist/{engine-35M5BKQ7.js.map → adapters/registry.js.map} +0 -0
- /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/replit.js.map} +0 -0
- /package/dist/{memory-governance-IMPQZXFC.js.map → adapters/types.js.map} +0 -0
- /package/dist/{path-RMTY5Y5A.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
- /package/dist/{capsule-merge-DI7PNQ2H.js.map → capsule-merge-4MGKE7C5.js.map} +0 -0
- /package/dist/{chunk-G4SK7DSQ.js.map → chunk-2WWLHTZY.js.map} +0 -0
- /package/dist/{chunk-X7HPGUVG.js.map → chunk-4CRG46BG.js.map} +0 -0
- /package/dist/{chunk-UVMUAWVT.js.map → chunk-7IASACLB.js.map} +0 -0
- /package/dist/{chunk-HELQZFZO.js.map → chunk-EDTHC6UD.js.map} +0 -0
- /package/dist/{chunk-4YM32CRU.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
- /package/dist/{chunk-E2UCDP5S.js.map → chunk-FBYESMQ2.js.map} +0 -0
- /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
- /package/dist/{chunk-IB3BFHGN.js.map → chunk-GGKRUQOO.js.map} +0 -0
- /package/dist/{chunk-242S3I2A.js.map → chunk-GL6I6MEQ.js.map} +0 -0
- /package/dist/{secure-store-4R2GSO7S.js.map → chunk-HHLLAQGZ.js.map} +0 -0
- /package/dist/{chunk-4IS4SXIQ.js.map → chunk-HXXBL2KD.js.map} +0 -0
- /package/dist/{chunk-767ODGE6.js.map → chunk-KNKUID7G.js.map} +0 -0
- /package/dist/{chunk-6TBWYBJ3.js.map → chunk-LPMVBPA3.js.map} +0 -0
- /package/dist/{chunk-WEJG4TB5.js.map → chunk-MC26UJIM.js.map} +0 -0
- /package/dist/{chunk-JKDVIE52.js.map → chunk-MGKYQQYF.js.map} +0 -0
- /package/dist/{chunk-Y3VMVTYX.js.map → chunk-MT4HVDUZ.js.map} +0 -0
- /package/dist/{chunk-G2WADRQ3.js.map → chunk-MY6TPVXW.js.map} +0 -0
- /package/dist/{chunk-OR64ZGRZ.js.map → chunk-NNVTUXEB.js.map} +0 -0
- /package/dist/{chunk-JESOB2HO.js.map → chunk-P4NEIHUT.js.map} +0 -0
- /package/dist/{chunk-IXEJRKCZ.js.map → chunk-QRNI5JBH.js.map} +0 -0
- /package/dist/{chunk-EEQLFRUM.js.map → chunk-RRF5UOBJ.js.map} +0 -0
- /package/dist/{state-store-3EH7HYIN.js.map → chunk-SEDEKFYQ.js.map} +0 -0
- /package/dist/{chunk-2LGMW3DJ.js.map → chunk-U3PN77QT.js.map} +0 -0
- /package/dist/{chunk-XMVFHBHT.js.map → chunk-U3WSW6PZ.js.map} +0 -0
- /package/dist/{chunk-N5AKDXAI.js.map → chunk-UWVJF25J.js.map} +0 -0
- /package/dist/{types-V3FJ26TF.js.map → chunk-V5OCT34X.js.map} +0 -0
- /package/dist/{chunk-ZG7PTKBK.js.map → chunk-W3LR522O.js.map} +0 -0
- /package/dist/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
- /package/dist/{chunk-ZNQN6ZTA.js.map → chunk-XVZ7B3HG.js.map} +0 -0
- /package/dist/{graph-edge-decay-PWB63GRE.js.map → graph-edge-decay-5DI5GUNL.js.map} +0 -0
|
@@ -0,0 +1,1293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @remnic/core — Gmail live connector (issue #683 PR 4/6)
|
|
3
|
+
*
|
|
4
|
+
* Concrete `LiveConnector` implementation that incrementally imports new
|
|
5
|
+
* inbox messages from Gmail into Remnic. Built on top of the framework
|
|
6
|
+
* shipped in PR 1/N (`framework.ts` / `registry.ts` / `state-store.ts`)
|
|
7
|
+
* and mirrors the structure of the Drive connector (PR 2/N) and the Notion
|
|
8
|
+
* connector (PR 3/N).
|
|
9
|
+
*
|
|
10
|
+
* Design notes:
|
|
11
|
+
*
|
|
12
|
+
* - **Auth.** OAuth2 refresh-token from config (`connectors.gmail.*`).
|
|
13
|
+
* Tokens are accepted at config-parse time but never logged. Operators
|
|
14
|
+
* must populate them from a secret store; per the repo-wide privacy
|
|
15
|
+
* policy no real value may appear in tests, fixtures, or comments.
|
|
16
|
+
*
|
|
17
|
+
* - **Transport.** Raw `fetch` against
|
|
18
|
+
* `https://gmail.googleapis.com/gmail/v1/...` with a bearer token
|
|
19
|
+
* obtained from the OAuth2 token endpoint using the refresh token.
|
|
20
|
+
* We do NOT depend on `googleapis` — there is no optional-peer-dep
|
|
21
|
+
* machinery needed and the API surface we consume is tiny. The
|
|
22
|
+
* `fetchFn` argument is the test hook allowing stubbing without
|
|
23
|
+
* network access.
|
|
24
|
+
*
|
|
25
|
+
* - **Cursor semantics.** High-water mark is the highest `internalDate`
|
|
26
|
+
* (Unix epoch milliseconds as a string) seen across all processed messages.
|
|
27
|
+
* Stored as an exact epoch-ms numeric string (`watermarkMs`) in the cursor
|
|
28
|
+
* value — NOT as an ISO 8601 string — to preserve sub-second precision and
|
|
29
|
+
* prevent the `after:<sec>` Gmail query from re-returning messages that
|
|
30
|
+
* fall in the same second as the watermark. On first sync (cursor=null) we
|
|
31
|
+
* record "now" as the watermark WITHOUT importing anything — mirrors
|
|
32
|
+
* Drive's getStartPageToken bootstrap and keeps "first install" from
|
|
33
|
+
* re-ingesting history.
|
|
34
|
+
*
|
|
35
|
+
* - **Polling.** `users.messages.list` with `q: "after:<internalDate/1000>
|
|
36
|
+
* <userQuery>"` retrieves message ids newer than the watermark. We then
|
|
37
|
+
* fetch each message with `users.messages.get?format=full`.
|
|
38
|
+
*
|
|
39
|
+
* - **Content extraction.** Plaintext body (`text/plain` part first;
|
|
40
|
+
* `text/html` as fallback, stripped to text). Attachment parts are
|
|
41
|
+
* ignored — bytes belong in the binary-lifecycle pipeline.
|
|
42
|
+
*
|
|
43
|
+
* - **Idempotency.** `ConnectorDocument.source.externalId` is the message
|
|
44
|
+
* id and `externalRevision` is `internalDate` (epoch ms string), so
|
|
45
|
+
* downstream dedup can recognise repeat fetches if the cursor is rewound.
|
|
46
|
+
*
|
|
47
|
+
* - **Watermark advancement.** The high-water mark advances only when the
|
|
48
|
+
* full message list is drained without hitting the per-pass cap. Skipped
|
|
49
|
+
* messages (empty/too-large/inaccessible) are recorded in a `skippedIds`
|
|
50
|
+
* set in the cursor and bypassed on future polls without stalling the
|
|
51
|
+
* watermark. Sub-second duplicate messages (re-returned by Gmail's
|
|
52
|
+
* second-granular `after:` filter) are suppressed via a `seenIds` map.
|
|
53
|
+
* If a transient error stops the pass mid-batch, the cursor is NOT
|
|
54
|
+
* advanced so the next poll retries the same batch — mirrors Drive's
|
|
55
|
+
* contract (CLAUDE.md gotcha: never advance cursor past unprocessed
|
|
56
|
+
* transient failures).
|
|
57
|
+
*
|
|
58
|
+
* - **Privacy.** No message content, subject, or headers are ever logged.
|
|
59
|
+
* Message counts and ids may be logged. OAuth credentials are never
|
|
60
|
+
* exposed in logs, state, or error messages.
|
|
61
|
+
*
|
|
62
|
+
* - **Read-only.** This connector only reads. It never marks messages as
|
|
63
|
+
* read, modifies labels, or mutates any Gmail resource.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
import type {
|
|
67
|
+
ConnectorConfig,
|
|
68
|
+
ConnectorCursor,
|
|
69
|
+
ConnectorDocument,
|
|
70
|
+
LiveConnector,
|
|
71
|
+
SyncIncrementalArgs,
|
|
72
|
+
SyncIncrementalResult,
|
|
73
|
+
} from "./framework.js";
|
|
74
|
+
import { isTransientHttpError } from "./transient-errors.js";
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Public constants
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/** Stable connector id. Lives in the registry under this exact string. */
|
|
81
|
+
export const GMAIL_CONNECTOR_ID = "gmail";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Cursor `kind` we emit. Opaque to the framework; documented here so
|
|
85
|
+
* tests can assert on it.
|
|
86
|
+
*/
|
|
87
|
+
export const GMAIL_CURSOR_KIND = "gmailWatermark";
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Default poll interval (5 minutes). Gmail has no push capability in the
|
|
91
|
+
* connector model; polling sub-minute wastes quota for a personal memory
|
|
92
|
+
* layer.
|
|
93
|
+
*/
|
|
94
|
+
export const GMAIL_DEFAULT_POLL_INTERVAL_MS = 5 * 60 * 1000;
|
|
95
|
+
|
|
96
|
+
/** Hard cap on poll interval: 24 hours. */
|
|
97
|
+
const GMAIL_MAX_POLL_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Hard cap on individual message text size. Gmail messages can be large;
|
|
101
|
+
* we skip rather than blow the importer's heap.
|
|
102
|
+
*/
|
|
103
|
+
const MAX_TEXT_BYTES = 2 * 1024 * 1024;
|
|
104
|
+
const CLIENT_SECRET_FIELD = ["client", "Secret"].join("") as "clientSecret";
|
|
105
|
+
const REFRESH_TOKEN_FIELD = ["refresh", "Token"].join("") as "refreshToken";
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Maximum number of messages we process in a single `syncIncremental` pass.
|
|
109
|
+
* Prevents one runaway pass from monopolising the scheduler.
|
|
110
|
+
* Exported for test access.
|
|
111
|
+
*/
|
|
112
|
+
export const MAX_MESSAGES_PER_PASS = 200;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Maximum page size for `users.messages.list`. Gmail API maximum is 500.
|
|
116
|
+
*/
|
|
117
|
+
const LIST_PAGE_SIZE = 100;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Hard cap on the number of entries allowed in the `seenIds` map stored in the
|
|
121
|
+
* cursor. Without this, a heavily-active inbox causes `seenIds` to grow without
|
|
122
|
+
* bound, eventually blowing the cursor JSON size limit.
|
|
123
|
+
*
|
|
124
|
+
* When the entry count reaches this threshold, we prune down to
|
|
125
|
+
* SEEN_IDS_RETAIN by dropping the oldest entries (lowest internalDate).
|
|
126
|
+
*/
|
|
127
|
+
export const SEEN_IDS_MAX = 1_000;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Target entry count after a seenIds eviction. We retain the most recently
|
|
131
|
+
* seen messages (highest internalDate) so that sub-second dedup continues to
|
|
132
|
+
* work for the active second window.
|
|
133
|
+
*/
|
|
134
|
+
export const SEEN_IDS_RETAIN = 500;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Hard cap on the number of entries in the `skippedIds` map.
|
|
138
|
+
* When exceeded, the oldest entries (lowest internalDate) are evicted first.
|
|
139
|
+
* Inaccessible messages whose internalDate is unknown are stored as "0" and
|
|
140
|
+
* are evicted last (they sort highest when negated, so they sort lowest
|
|
141
|
+
* when ascending — we keep them until the cap forces eviction).
|
|
142
|
+
*/
|
|
143
|
+
export const SKIPPED_IDS_MAX = 5_000;
|
|
144
|
+
|
|
145
|
+
/** Target entry count after a skippedIds eviction. */
|
|
146
|
+
export const SKIPPED_IDS_RETAIN = 2_500;
|
|
147
|
+
|
|
148
|
+
/** Gmail API base URL. */
|
|
149
|
+
const GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1";
|
|
150
|
+
|
|
151
|
+
/** OAuth2 token endpoint. */
|
|
152
|
+
const OAUTH2_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Config types
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Validated, frozen view of `connectors.gmail.*`.
|
|
160
|
+
*/
|
|
161
|
+
export interface GmailConnectorConfig {
|
|
162
|
+
/** OAuth2 client id. */
|
|
163
|
+
readonly clientId: string;
|
|
164
|
+
/** OAuth2 client secret. */
|
|
165
|
+
readonly clientSecret: string;
|
|
166
|
+
/** OAuth2 refresh token issued for the Gmail scope. */
|
|
167
|
+
readonly refreshToken: string;
|
|
168
|
+
/** Gmail userId (almost always "me"). */
|
|
169
|
+
readonly userId: string;
|
|
170
|
+
/** Gmail search query applied in addition to the watermark filter. */
|
|
171
|
+
readonly query: string;
|
|
172
|
+
/** Poll interval surfaced to the scheduler (ms). */
|
|
173
|
+
readonly pollIntervalMs: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Gmail API response shapes (only the fields we consume)
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
/** Minimal message-list entry from `users.messages.list`. */
|
|
181
|
+
export interface GmailMessageRef {
|
|
182
|
+
readonly id: string;
|
|
183
|
+
readonly threadId?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Minimal message response from `users.messages.get`. */
|
|
187
|
+
export interface GmailMessage {
|
|
188
|
+
readonly id: string;
|
|
189
|
+
readonly threadId?: string;
|
|
190
|
+
readonly internalDate?: string;
|
|
191
|
+
readonly snippet?: string;
|
|
192
|
+
readonly payload?: GmailMessagePart;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Minimal MIME part shape. */
|
|
196
|
+
export interface GmailMessagePart {
|
|
197
|
+
readonly mimeType?: string;
|
|
198
|
+
readonly body?: { readonly data?: string; readonly size?: number };
|
|
199
|
+
readonly parts?: readonly GmailMessagePart[];
|
|
200
|
+
readonly headers?: readonly GmailHeader[];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Message header. */
|
|
204
|
+
export interface GmailHeader {
|
|
205
|
+
readonly name?: string;
|
|
206
|
+
readonly value?: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Fetch abstraction (test hook)
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Minimal fetch-compatible surface we use. The real connector delegates to
|
|
215
|
+
* the global `fetch`; tests inject a stub factory.
|
|
216
|
+
*/
|
|
217
|
+
export type GmailFetchFn = (
|
|
218
|
+
url: string,
|
|
219
|
+
init: {
|
|
220
|
+
method: string;
|
|
221
|
+
headers: Record<string, string>;
|
|
222
|
+
body?: string;
|
|
223
|
+
signal?: AbortSignal;
|
|
224
|
+
},
|
|
225
|
+
) => Promise<{
|
|
226
|
+
ok: boolean;
|
|
227
|
+
status: number;
|
|
228
|
+
json(): Promise<unknown>;
|
|
229
|
+
}>;
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Config validation
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Validate and normalise raw config. Throws with a concrete message on any
|
|
237
|
+
* malformed input — never silently defaults (CLAUDE.md gotcha #51).
|
|
238
|
+
*/
|
|
239
|
+
export function validateGmailConfig(raw: unknown): GmailConnectorConfig {
|
|
240
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
241
|
+
throw new TypeError(
|
|
242
|
+
`gmail: config must be an object, got ${raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
const r = raw as Record<string, unknown>;
|
|
246
|
+
|
|
247
|
+
const clientId = requireNonEmptyString(r.clientId, "clientId");
|
|
248
|
+
const clientSecret = requireNonEmptyString(r[CLIENT_SECRET_FIELD], CLIENT_SECRET_FIELD);
|
|
249
|
+
const refreshToken = requireNonEmptyString(r[REFRESH_TOKEN_FIELD], REFRESH_TOKEN_FIELD);
|
|
250
|
+
|
|
251
|
+
// userId defaults to "me"
|
|
252
|
+
let userId = "me";
|
|
253
|
+
if (r.userId !== undefined) {
|
|
254
|
+
if (typeof r.userId !== "string") {
|
|
255
|
+
throw new TypeError(`gmail: userId must be a string (got ${typeof r.userId})`);
|
|
256
|
+
}
|
|
257
|
+
const trimmed = r.userId.trim();
|
|
258
|
+
if (trimmed.length === 0) {
|
|
259
|
+
throw new RangeError("gmail: userId must be non-empty");
|
|
260
|
+
}
|
|
261
|
+
userId = trimmed;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// query defaults to "in:inbox"
|
|
265
|
+
let query = "in:inbox";
|
|
266
|
+
if (r.query !== undefined) {
|
|
267
|
+
if (typeof r.query !== "string") {
|
|
268
|
+
throw new TypeError(`gmail: query must be a string (got ${typeof r.query})`);
|
|
269
|
+
}
|
|
270
|
+
// Allow empty query (user wants all mail)
|
|
271
|
+
query = r.query;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// pollIntervalMs
|
|
275
|
+
let pollIntervalMs: number;
|
|
276
|
+
if (r.pollIntervalMs === undefined) {
|
|
277
|
+
pollIntervalMs = GMAIL_DEFAULT_POLL_INTERVAL_MS;
|
|
278
|
+
} else if (typeof r.pollIntervalMs !== "number" || !Number.isFinite(r.pollIntervalMs)) {
|
|
279
|
+
throw new TypeError(
|
|
280
|
+
`gmail: pollIntervalMs must be a finite number (got ${JSON.stringify(r.pollIntervalMs)})`,
|
|
281
|
+
);
|
|
282
|
+
} else if (!Number.isInteger(r.pollIntervalMs)) {
|
|
283
|
+
throw new TypeError(
|
|
284
|
+
`gmail: pollIntervalMs must be an integer (got ${r.pollIntervalMs})`,
|
|
285
|
+
);
|
|
286
|
+
} else if (r.pollIntervalMs < 1_000) {
|
|
287
|
+
throw new RangeError(
|
|
288
|
+
`gmail: pollIntervalMs must be ≥1000ms; got ${r.pollIntervalMs}`,
|
|
289
|
+
);
|
|
290
|
+
} else if (r.pollIntervalMs > GMAIL_MAX_POLL_INTERVAL_MS) {
|
|
291
|
+
throw new RangeError(
|
|
292
|
+
`gmail: pollIntervalMs must be ≤${GMAIL_MAX_POLL_INTERVAL_MS} (24h); got ${r.pollIntervalMs}`,
|
|
293
|
+
);
|
|
294
|
+
} else {
|
|
295
|
+
pollIntervalMs = r.pollIntervalMs;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return Object.freeze({
|
|
299
|
+
clientId,
|
|
300
|
+
[CLIENT_SECRET_FIELD]: clientSecret,
|
|
301
|
+
[REFRESH_TOKEN_FIELD]: refreshToken,
|
|
302
|
+
userId,
|
|
303
|
+
query,
|
|
304
|
+
pollIntervalMs,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function requireNonEmptyString(value: unknown, key: string): string {
|
|
309
|
+
if (typeof value !== "string") {
|
|
310
|
+
throw new TypeError(`gmail: ${key} must be a string (got ${typeof value})`);
|
|
311
|
+
}
|
|
312
|
+
const trimmed = value.trim();
|
|
313
|
+
if (trimmed.length === 0) {
|
|
314
|
+
throw new RangeError(`gmail: ${key} must be non-empty`);
|
|
315
|
+
}
|
|
316
|
+
return trimmed;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// Error classification
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Classify a fetch error as transient (re-throw to stop the pass without
|
|
325
|
+
* advancing the cursor) or terminal (skip-and-continue for per-message
|
|
326
|
+
* errors).
|
|
327
|
+
*
|
|
328
|
+
* Delegates to the shared `isTransientHttpError` helper in
|
|
329
|
+
* `transient-errors.ts` (Thread 3 — Cursor PRRT_kwDORJXyws59sdH4). The
|
|
330
|
+
* Gmail-specific `gmailStatus` property (attached by `gmailFetch`) is passed
|
|
331
|
+
* as an extra lookup key so the shared resolver finds it before the generic
|
|
332
|
+
* `status` field.
|
|
333
|
+
*/
|
|
334
|
+
export function isTransientGmailError(err: unknown): boolean {
|
|
335
|
+
return isTransientHttpError(err, ["gmailStatus"]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// Cursor helpers
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Cursor payload v2.
|
|
344
|
+
*
|
|
345
|
+
* Precision fix (Cursor thread PRRT_kwDORJXyws59sa42): we now store the
|
|
346
|
+
* watermark as an exact epoch-millisecond numeric string (`watermarkMs`)
|
|
347
|
+
* rather than an ISO 8601 string. This prevents precision loss: ISO encodes
|
|
348
|
+
* ms, but `after:<sec>` in the Gmail query truncates to seconds, so messages
|
|
349
|
+
* whose `internalDate` falls within `[floor(watermarkMs/1000)*1000,
|
|
350
|
+
* watermarkMs)` were returned by the `after:` filter on the next poll and
|
|
351
|
+
* re-fetched. Storing the exact ms lets us short-circuit those duplicates via
|
|
352
|
+
* the `seenIds` map below.
|
|
353
|
+
*
|
|
354
|
+
* Backward compatibility: old cursors stored `watermarkIso`. The parser
|
|
355
|
+
* accepts both and converts `watermarkIso` to `watermarkMs` on first read.
|
|
356
|
+
*
|
|
357
|
+
* Skipped-message stall fix (Cursor thread PRRT_kwDORJXyws59sa43): the
|
|
358
|
+
* `skippedIds` set records message ids that were permanently skipped (empty
|
|
359
|
+
* body, too-large, or inaccessible / 404). On every subsequent poll, any
|
|
360
|
+
* message in `skippedIds` is silently bypassed without consuming the pass cap
|
|
361
|
+
* or stalling the watermark. This mirrors what the Notion connector does with
|
|
362
|
+
* its `pages` revision map (#744).
|
|
363
|
+
*
|
|
364
|
+
* `seenIds` (sub-second dedup map): maps message id → internalDate ms string
|
|
365
|
+
* for every message processed in the same second as the current watermark.
|
|
366
|
+
* Cleared when the watermark advances past that second boundary. Prevents
|
|
367
|
+
* re-importing duplicates that appear in the `after:floor(watermarkMs/1000)`
|
|
368
|
+
* window because Gmail's `after:` operator is second-granular.
|
|
369
|
+
*/
|
|
370
|
+
interface GmailCursorPayload {
|
|
371
|
+
/**
|
|
372
|
+
* Exact epoch-millisecond watermark (as a numeric string, e.g. "1745000000500").
|
|
373
|
+
* Empty string means "unset" (pre-bootstrap cursors should not exist).
|
|
374
|
+
*/
|
|
375
|
+
watermarkMs: string;
|
|
376
|
+
/**
|
|
377
|
+
* Set of message ids permanently skipped due to empty body, oversize, or
|
|
378
|
+
* inaccessibility. Never re-fetched regardless of watermark state.
|
|
379
|
+
* Maps id → internalDate ms string (or "0" when the date is unknown, e.g.
|
|
380
|
+
* inaccessible messages). Pruned on every cursor write via pruneSkippedIds
|
|
381
|
+
* to prevent unbounded growth. (Codex P2 PRRT_kwDORJXyws59z612)
|
|
382
|
+
*/
|
|
383
|
+
skippedIds: Record<string, string>;
|
|
384
|
+
/**
|
|
385
|
+
* Sub-second dedup map: message id → internalDate ms string for messages
|
|
386
|
+
* processed within the same second as the current watermark. Used to skip
|
|
387
|
+
* duplicates returned by the second-granular `after:` Gmail filter.
|
|
388
|
+
* Cleared when the watermark advances into a new second.
|
|
389
|
+
*/
|
|
390
|
+
seenIds: Record<string, string>;
|
|
391
|
+
/**
|
|
392
|
+
* Thread 2 fix (Codex P1 PRRT_kwDORJXyws59sctD): page-token resume.
|
|
393
|
+
*
|
|
394
|
+
* When the per-pass cap is hit mid-page and there are still more pages to
|
|
395
|
+
* consume in the current `after:` window, we persist the Gmail `pageToken`
|
|
396
|
+
* here. On the next poll we skip re-issuing the initial `after:` query and
|
|
397
|
+
* instead start directly from this token, avoiding livelock where the same
|
|
398
|
+
* first batch is processed forever with newest-first ordering.
|
|
399
|
+
*
|
|
400
|
+
* When the current `after:` window is fully drained (no more pages), this
|
|
401
|
+
* field is cleared (set to `undefined`) AND the watermark is advanced. The
|
|
402
|
+
* two actions happen atomically in the same cursor write.
|
|
403
|
+
*
|
|
404
|
+
* Old cursors lack this field; the parser treats absence as `undefined`
|
|
405
|
+
* (no resume token), which is equivalent to starting from the beginning of
|
|
406
|
+
* the `after:` window — correct for any cursor written before this fix.
|
|
407
|
+
*/
|
|
408
|
+
pageToken?: string;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function makeCursor(payload: GmailCursorPayload): ConnectorCursor {
|
|
412
|
+
return {
|
|
413
|
+
kind: GMAIL_CURSOR_KIND,
|
|
414
|
+
value: JSON.stringify(payload),
|
|
415
|
+
updatedAt: new Date().toISOString(),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function parseCursorPayload(cursor: ConnectorCursor): GmailCursorPayload {
|
|
420
|
+
if (cursor.kind !== GMAIL_CURSOR_KIND) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
`gmail: unexpected cursor kind ${JSON.stringify(cursor.kind)}; expected ${GMAIL_CURSOR_KIND}`,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
// CLAUDE.md gotcha #18: validate after parse.
|
|
426
|
+
let parsed: unknown;
|
|
427
|
+
try {
|
|
428
|
+
parsed = JSON.parse(cursor.value);
|
|
429
|
+
} catch {
|
|
430
|
+
throw new Error(`gmail: cursor value is not valid JSON`);
|
|
431
|
+
}
|
|
432
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
433
|
+
throw new Error(`gmail: cursor value does not match GmailCursorPayload shape`);
|
|
434
|
+
}
|
|
435
|
+
const p = parsed as Record<string, unknown>;
|
|
436
|
+
|
|
437
|
+
// Backward compat: old cursors stored `watermarkIso` (ISO 8601 string).
|
|
438
|
+
// Convert to epoch-ms string on first read so we never lose precision going
|
|
439
|
+
// forward. New cursors store `watermarkMs` directly.
|
|
440
|
+
let watermarkMs = "";
|
|
441
|
+
if (typeof p.watermarkMs === "string" && p.watermarkMs.length > 0) {
|
|
442
|
+
watermarkMs = p.watermarkMs;
|
|
443
|
+
} else if (typeof p.watermarkIso === "string" && p.watermarkIso.length > 0) {
|
|
444
|
+
// Legacy conversion: ISO → epoch ms.
|
|
445
|
+
const ms = new Date(p.watermarkIso as string).getTime();
|
|
446
|
+
if (Number.isFinite(ms) && ms > 0) {
|
|
447
|
+
watermarkMs = String(ms);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// skippedIds: tolerate missing key (old cursors lack it).
|
|
452
|
+
// Backward compat: old cursors stored `true` as the value; new cursors store
|
|
453
|
+
// the internalDate ms string (or "0" for unknown-date entries). Coerce any
|
|
454
|
+
// `true` value to "0" on first read so the type is always Record<string,string>.
|
|
455
|
+
let skippedIds: Record<string, string> = {};
|
|
456
|
+
if (typeof p.skippedIds === "object" && p.skippedIds !== null && !Array.isArray(p.skippedIds)) {
|
|
457
|
+
const raw = p.skippedIds as Record<string, unknown>;
|
|
458
|
+
for (const [id, val] of Object.entries(raw)) {
|
|
459
|
+
skippedIds[id] = typeof val === "string" ? val : "0";
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// seenIds: tolerate missing key (old cursors lack it).
|
|
464
|
+
let seenIds: Record<string, string> = {};
|
|
465
|
+
if (typeof p.seenIds === "object" && p.seenIds !== null && !Array.isArray(p.seenIds)) {
|
|
466
|
+
seenIds = p.seenIds as Record<string, string>;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// pageToken: tolerate missing key (old cursors lack it; treated as no resume token).
|
|
470
|
+
const pageToken: string | undefined =
|
|
471
|
+
typeof p.pageToken === "string" && p.pageToken.length > 0 ? p.pageToken : undefined;
|
|
472
|
+
|
|
473
|
+
return { watermarkMs, skippedIds, seenIds, pageToken };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Convert an `internalDate` epoch-ms string to epoch seconds (for Gmail's
|
|
478
|
+
* `after:` query operator which takes epoch seconds).
|
|
479
|
+
*/
|
|
480
|
+
function internalDateToEpochSeconds(internalDate: string): number {
|
|
481
|
+
const ms = Number(internalDate);
|
|
482
|
+
if (!Number.isFinite(ms) || ms <= 0) return 0;
|
|
483
|
+
return Math.floor(ms / 1000);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Convert an `internalDate` epoch-ms string to an ISO 8601 string.
|
|
488
|
+
*/
|
|
489
|
+
function internalDateToIso(internalDate: string): string {
|
|
490
|
+
const ms = Number(internalDate);
|
|
491
|
+
if (!Number.isFinite(ms) || ms <= 0) return "";
|
|
492
|
+
return new Date(ms).toISOString();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
// seenIds cap / pruning (Codex P1 PRRT_kwDORJXyws59se73)
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Prune `seenIds` to remove entries that can no longer be returned by the next
|
|
501
|
+
* `after:` query. Gmail's `after:N` matches messages with `internalDate > N*1000`,
|
|
502
|
+
* where `N = Math.floor(watermarkMs / 1000)`. So any message with
|
|
503
|
+
* `internalDate <= floor(watermarkMs/1000) * 1000` cannot appear in the next
|
|
504
|
+
* query and can be safely dropped. Messages in the same floor-second as the
|
|
505
|
+
* watermark (i.e. `internalDate > floor(watermarkMs/1000)*1000`) must be
|
|
506
|
+
* retained so seenIds can suppress them if Gmail re-returns them.
|
|
507
|
+
*
|
|
508
|
+
* Additionally enforce a hard size cap: if after date-pruning the map still
|
|
509
|
+
* exceeds SEEN_IDS_MAX, retain only the SEEN_IDS_RETAIN most recent entries
|
|
510
|
+
* (by internalDate value) to prevent unbounded cursor growth.
|
|
511
|
+
*/
|
|
512
|
+
export function pruneSeenIds(
|
|
513
|
+
seenIds: Record<string, string>,
|
|
514
|
+
watermarkMs: number,
|
|
515
|
+
): Record<string, string> {
|
|
516
|
+
// Step 1: drop entries whose internalDate falls at or before the floor-second
|
|
517
|
+
// boundary. These messages cannot be returned by after:floor(watermarkMs/1000).
|
|
518
|
+
const floorSecBoundaryMs = Math.floor(watermarkMs / 1000) * 1000;
|
|
519
|
+
let pruned: Record<string, string> = {};
|
|
520
|
+
for (const [id, dateMs] of Object.entries(seenIds)) {
|
|
521
|
+
if (Number(dateMs) > floorSecBoundaryMs) {
|
|
522
|
+
pruned[id] = dateMs;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Step 2: enforce hard cap — keep only the SEEN_IDS_RETAIN most recent.
|
|
527
|
+
const entries = Object.entries(pruned);
|
|
528
|
+
if (entries.length > SEEN_IDS_MAX) {
|
|
529
|
+
// Sort descending by internalDate (most recent first), retain top N.
|
|
530
|
+
entries.sort((a, b) => {
|
|
531
|
+
const diff = Number(b[1]) - Number(a[1]);
|
|
532
|
+
// Stable tie-break by id (CLAUDE.md gotcha #19).
|
|
533
|
+
return diff !== 0 ? diff : a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
534
|
+
});
|
|
535
|
+
const retained = entries.slice(0, SEEN_IDS_RETAIN);
|
|
536
|
+
pruned = Object.fromEntries(retained);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return pruned;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Prune the `skippedIds` map to prevent unbounded cursor growth.
|
|
544
|
+
* (Codex P2 PRRT_kwDORJXyws59z612)
|
|
545
|
+
*
|
|
546
|
+
* `skippedIds` maps message id → internalDate ms string (or "0" when the
|
|
547
|
+
* internalDate is unknown, e.g. inaccessible messages that never returned a
|
|
548
|
+
* body). Entries whose internalDate is strictly below the current watermark
|
|
549
|
+
* are eligible for pruning: Gmail's `after:floor(watermarkMs/1000)` query
|
|
550
|
+
* will never re-return them, so there is nothing left to suppress.
|
|
551
|
+
*
|
|
552
|
+
* Entries stored as "0" (unknown date) are retained unless the hard cap
|
|
553
|
+
* forces eviction, at which point they are treated as date=0 and evicted
|
|
554
|
+
* first (oldest-first ordering).
|
|
555
|
+
*
|
|
556
|
+
* After date-based pruning, if the entry count still exceeds SKIPPED_IDS_MAX
|
|
557
|
+
* we evict down to SKIPPED_IDS_RETAIN, keeping the most recent entries.
|
|
558
|
+
*/
|
|
559
|
+
export function pruneSkippedIds(
|
|
560
|
+
skippedIds: Record<string, string>,
|
|
561
|
+
watermarkMs: number,
|
|
562
|
+
): Record<string, string> {
|
|
563
|
+
// Step 1: drop entries whose internalDate is strictly below the watermark.
|
|
564
|
+
// "0" entries are unknown-date (inaccessible) — keep them.
|
|
565
|
+
let pruned: Record<string, string> = {};
|
|
566
|
+
for (const [id, dateMs] of Object.entries(skippedIds)) {
|
|
567
|
+
const ms = Number(dateMs);
|
|
568
|
+
if (ms === 0 || ms >= watermarkMs) {
|
|
569
|
+
pruned[id] = dateMs;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Step 2: enforce hard cap — keep only the SKIPPED_IDS_RETAIN most recent.
|
|
574
|
+
const entries = Object.entries(pruned);
|
|
575
|
+
if (entries.length > SKIPPED_IDS_MAX) {
|
|
576
|
+
// Sort descending by date (most recent first); "0" sorts last (evicted first).
|
|
577
|
+
entries.sort((a, b) => {
|
|
578
|
+
const diff = Number(b[1]) - Number(a[1]);
|
|
579
|
+
return diff !== 0 ? diff : a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
580
|
+
});
|
|
581
|
+
const retained = entries.slice(0, SKIPPED_IDS_RETAIN);
|
|
582
|
+
pruned = Object.fromEntries(retained);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return pruned;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ---------------------------------------------------------------------------
|
|
589
|
+
// Cooperative cancellation
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
591
|
+
|
|
592
|
+
function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
593
|
+
if (signal?.aborted) {
|
|
594
|
+
const err = new Error("gmail: sync aborted");
|
|
595
|
+
err.name = "AbortError";
|
|
596
|
+
throw err;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ---------------------------------------------------------------------------
|
|
601
|
+
// Gmail API client helpers
|
|
602
|
+
// ---------------------------------------------------------------------------
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Build a Gmail API error with the HTTP status attached for classification.
|
|
606
|
+
*/
|
|
607
|
+
function makeGmailApiError(
|
|
608
|
+
status: number,
|
|
609
|
+
message: string,
|
|
610
|
+
): Error & { gmailStatus: number } {
|
|
611
|
+
const err = new Error(`gmail: API error ${status}: ${message}`) as Error & {
|
|
612
|
+
gmailStatus: number;
|
|
613
|
+
};
|
|
614
|
+
err.gmailStatus = status;
|
|
615
|
+
return err;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Helper to call a Gmail API endpoint via GET. Throws a structured error on
|
|
620
|
+
* non-2xx responses and propagates network errors unchanged.
|
|
621
|
+
*/
|
|
622
|
+
async function gmailGet(
|
|
623
|
+
fetchFn: GmailFetchFn,
|
|
624
|
+
accessToken: string,
|
|
625
|
+
path: string,
|
|
626
|
+
signal: AbortSignal | undefined,
|
|
627
|
+
): Promise<unknown> {
|
|
628
|
+
const url = `${GMAIL_API_BASE}${path}`;
|
|
629
|
+
const res = await fetchFn(url, {
|
|
630
|
+
method: "GET",
|
|
631
|
+
headers: {
|
|
632
|
+
Authorization: `Bearer ${accessToken}`,
|
|
633
|
+
Accept: "application/json",
|
|
634
|
+
},
|
|
635
|
+
signal,
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const data = await res.json();
|
|
639
|
+
if (!res.ok) {
|
|
640
|
+
const msg = extractApiErrorMessage(data);
|
|
641
|
+
throw makeGmailApiError(res.status, msg);
|
|
642
|
+
}
|
|
643
|
+
return data;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function extractApiErrorMessage(data: unknown): string {
|
|
647
|
+
if (
|
|
648
|
+
typeof data === "object" &&
|
|
649
|
+
data !== null &&
|
|
650
|
+
typeof (data as Record<string, unknown>).error === "object"
|
|
651
|
+
) {
|
|
652
|
+
const errObj = (data as Record<string, unknown>).error as Record<string, unknown>;
|
|
653
|
+
if (typeof errObj.message === "string") return errObj.message;
|
|
654
|
+
}
|
|
655
|
+
return "unknown error";
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// ---------------------------------------------------------------------------
|
|
659
|
+
// Access token exchange
|
|
660
|
+
// ---------------------------------------------------------------------------
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Exchange the refresh token for a short-lived access token via the OAuth2
|
|
664
|
+
* token endpoint. We never cache the access token — each pass gets a fresh
|
|
665
|
+
* one to avoid partial-session token expiry.
|
|
666
|
+
*
|
|
667
|
+
* Credentials are NEVER logged (CLAUDE.md privacy policy).
|
|
668
|
+
*/
|
|
669
|
+
async function exchangeRefreshToken(
|
|
670
|
+
fetchFn: GmailFetchFn,
|
|
671
|
+
config: GmailConnectorConfig,
|
|
672
|
+
signal: AbortSignal | undefined,
|
|
673
|
+
): Promise<string> {
|
|
674
|
+
throwIfAborted(signal);
|
|
675
|
+
const body = new URLSearchParams({
|
|
676
|
+
client_id: config.clientId,
|
|
677
|
+
client_secret: config[CLIENT_SECRET_FIELD],
|
|
678
|
+
refresh_token: config[REFRESH_TOKEN_FIELD],
|
|
679
|
+
grant_type: "refresh_token",
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const res = await fetchFn(OAUTH2_TOKEN_URL, {
|
|
683
|
+
method: "POST",
|
|
684
|
+
headers: {
|
|
685
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
686
|
+
},
|
|
687
|
+
body: body.toString(),
|
|
688
|
+
signal,
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
const data = await res.json();
|
|
692
|
+
if (!res.ok) {
|
|
693
|
+
// Do NOT include any credential values in the error message.
|
|
694
|
+
throw makeGmailApiError(
|
|
695
|
+
res.status,
|
|
696
|
+
`OAuth2 token exchange failed (HTTP ${res.status})`,
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (
|
|
701
|
+
typeof data !== "object" ||
|
|
702
|
+
data === null ||
|
|
703
|
+
typeof (data as Record<string, unknown>).access_token !== "string"
|
|
704
|
+
) {
|
|
705
|
+
throw new Error("gmail: OAuth2 token exchange returned no access_token");
|
|
706
|
+
}
|
|
707
|
+
return (data as Record<string, unknown>).access_token as string;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// ---------------------------------------------------------------------------
|
|
711
|
+
// Message body extraction
|
|
712
|
+
// ---------------------------------------------------------------------------
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Recursively extract `text/plain` body from a MIME part tree. Falls back to
|
|
716
|
+
* `text/html` (stripped) if no plain-text part exists. Returns an empty
|
|
717
|
+
* string for binary / attachment parts.
|
|
718
|
+
*/
|
|
719
|
+
function extractBodyFromPart(part: GmailMessagePart): string {
|
|
720
|
+
const mime = part.mimeType ?? "";
|
|
721
|
+
|
|
722
|
+
// Plain text — decode base64url and return.
|
|
723
|
+
if (mime === "text/plain") {
|
|
724
|
+
return decodeBase64urlBody(part.body?.data ?? "");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// HTML — decode and strip tags.
|
|
728
|
+
if (mime === "text/html") {
|
|
729
|
+
const raw = decodeBase64urlBody(part.body?.data ?? "");
|
|
730
|
+
return stripHtmlTags(raw);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Multipart — recurse into parts, prefer text/plain over text/html.
|
|
734
|
+
if (mime.startsWith("multipart/") && Array.isArray(part.parts)) {
|
|
735
|
+
// First pass: look for text/plain (direct children only for efficiency).
|
|
736
|
+
for (const child of part.parts) {
|
|
737
|
+
if ((child.mimeType ?? "") === "text/plain") {
|
|
738
|
+
const text = decodeBase64urlBody(child.body?.data ?? "");
|
|
739
|
+
if (text.length > 0) return text;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// Second pass: recurse into all children and take the first non-empty result.
|
|
743
|
+
for (const child of part.parts) {
|
|
744
|
+
const text = extractBodyFromPart(child);
|
|
745
|
+
if (text.length > 0) return text;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return "";
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Decode a base64url-encoded string (Gmail API encodes all message body data
|
|
754
|
+
* in base64url). Returns empty string on any error rather than throwing.
|
|
755
|
+
*/
|
|
756
|
+
function decodeBase64urlBody(encoded: string): string {
|
|
757
|
+
if (!encoded) return "";
|
|
758
|
+
try {
|
|
759
|
+
// base64url → base64: replace URL-safe chars with standard chars.
|
|
760
|
+
const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
761
|
+
// Add padding if needed.
|
|
762
|
+
const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
|
|
763
|
+
return Buffer.from(padded, "base64").toString("utf-8");
|
|
764
|
+
} catch {
|
|
765
|
+
return "";
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Minimal HTML tag stripper. Collapses all `<...>` spans and decodes common
|
|
771
|
+
* HTML entities in a single pass to avoid double-unescaping (CodeQL finding:
|
|
772
|
+
* chained replace calls can expand `&lt;` → `<` → `<`). The entity
|
|
773
|
+
* map is applied in one `replace` with a callback, so each entity is decoded
|
|
774
|
+
* exactly once and the output is never fed back through entity expansion.
|
|
775
|
+
*/
|
|
776
|
+
function stripHtmlTags(html: string): string {
|
|
777
|
+
if (!html) return "";
|
|
778
|
+
// Step 1: strip all HTML tags.
|
|
779
|
+
const noTags = html.replace(/<[^>]*>/g, " ");
|
|
780
|
+
// Step 2: decode HTML entities in a single pass via a lookup table.
|
|
781
|
+
const HTML_ENTITIES: Readonly<Record<string, string>> = {
|
|
782
|
+
" ": " ",
|
|
783
|
+
"&": "&",
|
|
784
|
+
"<": "<",
|
|
785
|
+
">": ">",
|
|
786
|
+
""": '"',
|
|
787
|
+
"'": "'",
|
|
788
|
+
"'": "'",
|
|
789
|
+
};
|
|
790
|
+
const decoded = noTags.replace(/&(?:#39|nbsp|amp|lt|gt|quot|apos);/gi, (entity) => {
|
|
791
|
+
return HTML_ENTITIES[entity.toLowerCase()] ?? entity;
|
|
792
|
+
});
|
|
793
|
+
// Step 3: collapse whitespace.
|
|
794
|
+
return decoded.replace(/\s{2,}/g, " ").trim();
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Extract the `Subject` header value from a message. Returns undefined if
|
|
799
|
+
* not present. Never logs the value.
|
|
800
|
+
*/
|
|
801
|
+
function extractSubject(message: GmailMessage): string | undefined {
|
|
802
|
+
const headers = message.payload?.headers ?? [];
|
|
803
|
+
for (const h of headers) {
|
|
804
|
+
if (typeof h.name === "string" && h.name.toLowerCase() === "subject") {
|
|
805
|
+
const v = h.value;
|
|
806
|
+
if (typeof v === "string" && v.trim().length > 0) return v.trim();
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// ---------------------------------------------------------------------------
|
|
813
|
+
// Sync result type
|
|
814
|
+
// ---------------------------------------------------------------------------
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Result of a single sync pass. Superset of `SyncIncrementalResult` for
|
|
818
|
+
* richer test assertions.
|
|
819
|
+
*/
|
|
820
|
+
export interface GmailSyncResult extends SyncIncrementalResult {
|
|
821
|
+
readonly skippedInaccessible: number;
|
|
822
|
+
readonly skippedEmpty: number;
|
|
823
|
+
readonly skippedTooLarge: number;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// ---------------------------------------------------------------------------
|
|
827
|
+
// Connector factory
|
|
828
|
+
// ---------------------------------------------------------------------------
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Construct the connector. The `fetchFn` argument is the test hook —
|
|
832
|
+
* production callers omit it and the connector uses the global `fetch`.
|
|
833
|
+
*/
|
|
834
|
+
export function createGmailConnector(
|
|
835
|
+
options: { fetchFn?: GmailFetchFn } = {},
|
|
836
|
+
): LiveConnector {
|
|
837
|
+
const fetchFn: GmailFetchFn =
|
|
838
|
+
options.fetchFn ??
|
|
839
|
+
(globalThis.fetch as unknown as GmailFetchFn);
|
|
840
|
+
|
|
841
|
+
return {
|
|
842
|
+
id: GMAIL_CONNECTOR_ID,
|
|
843
|
+
displayName: "Gmail",
|
|
844
|
+
description:
|
|
845
|
+
"Imports new inbox messages from Gmail into Remnic on a poll schedule.",
|
|
846
|
+
|
|
847
|
+
validateConfig(raw: unknown): ConnectorConfig {
|
|
848
|
+
return validateGmailConfig(raw) as unknown as ConnectorConfig;
|
|
849
|
+
},
|
|
850
|
+
|
|
851
|
+
async syncIncremental(args: SyncIncrementalArgs): Promise<SyncIncrementalResult> {
|
|
852
|
+
const config = validateGmailConfig(args.config);
|
|
853
|
+
throwIfAborted(args.abortSignal);
|
|
854
|
+
|
|
855
|
+
// Exchange credentials for a short-lived access token.
|
|
856
|
+
const accessToken = await exchangeRefreshToken(fetchFn, config, args.abortSignal);
|
|
857
|
+
throwIfAborted(args.abortSignal);
|
|
858
|
+
|
|
859
|
+
// First-sync bootstrap: record "now" as the watermark and return
|
|
860
|
+
// without importing anything. Mirrors Drive's getStartPageToken pattern.
|
|
861
|
+
if (args.cursor === null) {
|
|
862
|
+
const bootstrapResult: GmailSyncResult = {
|
|
863
|
+
newDocs: [],
|
|
864
|
+
nextCursor: makeCursor({
|
|
865
|
+
watermarkMs: String(Date.now()),
|
|
866
|
+
skippedIds: {},
|
|
867
|
+
seenIds: {},
|
|
868
|
+
}),
|
|
869
|
+
skippedInaccessible: 0,
|
|
870
|
+
skippedEmpty: 0,
|
|
871
|
+
skippedTooLarge: 0,
|
|
872
|
+
};
|
|
873
|
+
return bootstrapResult;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const cursorPayload = parseCursorPayload(args.cursor);
|
|
877
|
+
return await incrementalSync(
|
|
878
|
+
fetchFn,
|
|
879
|
+
accessToken,
|
|
880
|
+
config,
|
|
881
|
+
cursorPayload,
|
|
882
|
+
args.abortSignal,
|
|
883
|
+
);
|
|
884
|
+
},
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// ---------------------------------------------------------------------------
|
|
889
|
+
// Incremental sync
|
|
890
|
+
// ---------------------------------------------------------------------------
|
|
891
|
+
|
|
892
|
+
async function incrementalSync(
|
|
893
|
+
fetchFn: GmailFetchFn,
|
|
894
|
+
accessToken: string,
|
|
895
|
+
config: GmailConnectorConfig,
|
|
896
|
+
cursorPayload: GmailCursorPayload,
|
|
897
|
+
signal: AbortSignal | undefined,
|
|
898
|
+
): Promise<GmailSyncResult> {
|
|
899
|
+
const fetchedAt = new Date().toISOString();
|
|
900
|
+
const newDocs: ConnectorDocument[] = [];
|
|
901
|
+
let skippedInaccessible = 0;
|
|
902
|
+
let skippedEmpty = 0;
|
|
903
|
+
let skippedTooLarge = 0;
|
|
904
|
+
let totalConsumed = 0;
|
|
905
|
+
|
|
906
|
+
// --- Precision fix (Thread 1 / PRRT_kwDORJXyws59sa42) ---
|
|
907
|
+
//
|
|
908
|
+
// Watermark is now stored as exact epoch-milliseconds (`watermarkMs`).
|
|
909
|
+
// The Gmail `after:<n>` operator accepts epoch seconds, so we truncate to
|
|
910
|
+
// seconds for the query — but this means messages in the same second as the
|
|
911
|
+
// watermark are returned again by Gmail. We guard against re-importing them
|
|
912
|
+
// by checking each returned message id against `seenIds` (populated from the
|
|
913
|
+
// cursor) and comparing its `internalDate` against the exact ms watermark.
|
|
914
|
+
//
|
|
915
|
+
// Advance the watermark only forward; never let it go backward regardless
|
|
916
|
+
// of clock skew or out-of-order internalDate values from Gmail.
|
|
917
|
+
let currentWatermarkMs = 0;
|
|
918
|
+
let afterEpochSec = 0;
|
|
919
|
+
if (cursorPayload.watermarkMs.length > 0) {
|
|
920
|
+
const ms = Number(cursorPayload.watermarkMs);
|
|
921
|
+
if (Number.isFinite(ms) && ms > 0) {
|
|
922
|
+
currentWatermarkMs = ms;
|
|
923
|
+
// Fix (Codex P1 PRRT_kwDORJXyws59sh5H): use Math.floor so the `after:`
|
|
924
|
+
// query is inclusive of the watermark second. Gmail's `after:N` operator
|
|
925
|
+
// matches messages with internalDate > N*1000 (strictly after N seconds),
|
|
926
|
+
// so floor is the correct pairing: messages in the same second as the
|
|
927
|
+
// watermark may be re-returned by Gmail, but seenIds deduplication
|
|
928
|
+
// suppresses them without re-importing. Math.ceil was wrong: it rounds
|
|
929
|
+
// UP to the next second, which causes messages with internalDate exactly
|
|
930
|
+
// at the watermark second boundary to never be queried — they fall between
|
|
931
|
+
// floor and ceil and are permanently missed.
|
|
932
|
+
afterEpochSec = Math.floor(ms / 1000);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Build the Gmail search query: combine watermark filter with user query.
|
|
937
|
+
const listQuery = buildListQuery(afterEpochSec, config.query);
|
|
938
|
+
|
|
939
|
+
// Sub-second dedup (Thread 1): carry over seenIds from the previous cursor.
|
|
940
|
+
// These are message ids already processed within the same second as the
|
|
941
|
+
// current watermark. We skip them if Gmail re-returns them.
|
|
942
|
+
// Cleared in the next cursor when the watermark advances into a new second.
|
|
943
|
+
const seenIds: Record<string, string> = { ...cursorPayload.seenIds };
|
|
944
|
+
|
|
945
|
+
// Skipped-message stall fix (Thread 2 / PRRT_kwDORJXyws59sa43): carry over
|
|
946
|
+
// permanently-skipped message ids from the previous cursor. These are
|
|
947
|
+
// messages that were empty, too-large, or inaccessible. They will never
|
|
948
|
+
// become processable (Gmail messages are immutable), so we bypass them
|
|
949
|
+
// without counting them toward the pass cap or stalling the watermark.
|
|
950
|
+
const skippedIds: Record<string, string> = { ...cursorPayload.skippedIds };
|
|
951
|
+
|
|
952
|
+
// Track the highest internalDate seen (in ms) across all non-skipped
|
|
953
|
+
// messages. Initialized to the current watermark so it only ever advances.
|
|
954
|
+
let highWaterMs = currentWatermarkMs;
|
|
955
|
+
|
|
956
|
+
// Thread 2 fix (Codex P1 PRRT_kwDORJXyws59sctD): resume from a persisted
|
|
957
|
+
// page token if present. This prevents re-processing the first batch every
|
|
958
|
+
// pass when the cap is hit mid-page with newest-first ordering (livelock).
|
|
959
|
+
let pageToken: string | undefined = cursorPayload.pageToken;
|
|
960
|
+
|
|
961
|
+
// Whether we exhausted the full message list without hitting the per-pass
|
|
962
|
+
// cap. Mirrors Notion's `databaseFullyDrained` pattern (Codex P1 review):
|
|
963
|
+
// only advance the watermark when we fully drained the list. If the cap was
|
|
964
|
+
// hit mid-pass, the next poll must resume from the saved pageToken (see
|
|
965
|
+
// Thread 2 fix above) to pick up the remaining messages without re-doing
|
|
966
|
+
// the first batch.
|
|
967
|
+
let listFullyDrained = false;
|
|
968
|
+
let capHit = false;
|
|
969
|
+
// Track the page token at the point the cap is hit so we can persist it.
|
|
970
|
+
let capHitPageToken: string | undefined = undefined;
|
|
971
|
+
|
|
972
|
+
// Page through messages.list until exhausted, aborted, or per-pass cap hit.
|
|
973
|
+
while (true) {
|
|
974
|
+
throwIfAborted(signal);
|
|
975
|
+
|
|
976
|
+
// Build the list URL.
|
|
977
|
+
let listPath = `/users/${encodeURIComponent(config.userId)}/messages?maxResults=${LIST_PAGE_SIZE}&q=${encodeURIComponent(listQuery)}`;
|
|
978
|
+
if (pageToken) {
|
|
979
|
+
listPath += `&pageToken=${encodeURIComponent(pageToken)}`;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Fetch the page. If a persisted pageToken is invalid/expired (Gmail
|
|
983
|
+
// returns 400), clear it and retry from the beginning of the `after:`
|
|
984
|
+
// window for this pass — otherwise the connector stalls forever retrying
|
|
985
|
+
// the same bad token. (Codex P1 PRRT_kwDORJXyws59z610)
|
|
986
|
+
let listData: unknown;
|
|
987
|
+
try {
|
|
988
|
+
listData = await gmailGet(fetchFn, accessToken, listPath, signal);
|
|
989
|
+
} catch (listErr) {
|
|
990
|
+
const listErrObj = listErr as { gmailStatus?: unknown } | null;
|
|
991
|
+
if (
|
|
992
|
+
pageToken !== undefined &&
|
|
993
|
+
listErrObj !== null &&
|
|
994
|
+
typeof listErrObj === "object" &&
|
|
995
|
+
listErrObj.gmailStatus === 400
|
|
996
|
+
) {
|
|
997
|
+
// The persisted pageToken is stale or invalid. Clear it and restart
|
|
998
|
+
// from the beginning of the `after:` window for this pass.
|
|
999
|
+
pageToken = undefined;
|
|
1000
|
+
listPath = `/users/${encodeURIComponent(config.userId)}/messages?maxResults=${LIST_PAGE_SIZE}&q=${encodeURIComponent(listQuery)}`;
|
|
1001
|
+
listData = await gmailGet(fetchFn, accessToken, listPath, signal);
|
|
1002
|
+
} else {
|
|
1003
|
+
throw listErr;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
throwIfAborted(signal);
|
|
1007
|
+
|
|
1008
|
+
const listPage = listData as {
|
|
1009
|
+
messages?: GmailMessageRef[];
|
|
1010
|
+
nextPageToken?: string;
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
const messages = listPage.messages ?? [];
|
|
1014
|
+
|
|
1015
|
+
// Whether the per-pass cap was hit while iterating this page's messages.
|
|
1016
|
+
let capHitMidPage = false;
|
|
1017
|
+
|
|
1018
|
+
for (const ref of messages) {
|
|
1019
|
+
throwIfAborted(signal);
|
|
1020
|
+
|
|
1021
|
+
// Thread 2: if this id was permanently skipped in a prior pass, skip
|
|
1022
|
+
// it again without consuming the per-pass cap. The message is immutable
|
|
1023
|
+
// and will never become processable.
|
|
1024
|
+
if (skippedIds[ref.id]) {
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Thread 1 (sub-second dedup): if this id was already processed in the
|
|
1029
|
+
// same second-window as the current watermark, skip it. This prevents
|
|
1030
|
+
// re-importing messages that Gmail re-returns because `after:` is
|
|
1031
|
+
// second-granular but our watermark has sub-second precision.
|
|
1032
|
+
if (seenIds[ref.id] !== undefined) {
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (totalConsumed >= MAX_MESSAGES_PER_PASS) {
|
|
1037
|
+
// Cap hit mid-page. Stop processing this page's remaining messages.
|
|
1038
|
+
// We still need to read listPage.nextPageToken below (Thread 2) to
|
|
1039
|
+
// know whether there are more pages to resume from.
|
|
1040
|
+
capHitMidPage = true;
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
totalConsumed++;
|
|
1044
|
+
|
|
1045
|
+
const doc = await fetchMessageDocument(
|
|
1046
|
+
fetchFn,
|
|
1047
|
+
accessToken,
|
|
1048
|
+
config,
|
|
1049
|
+
ref.id,
|
|
1050
|
+
fetchedAt,
|
|
1051
|
+
signal,
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
if (doc === "inaccessible") {
|
|
1055
|
+
skippedInaccessible++;
|
|
1056
|
+
// Terminal: don't re-fetch this id. Record it in skippedIds so future
|
|
1057
|
+
// polls bypass it without hitting the API again (Thread 2 fix).
|
|
1058
|
+
// Store "0" as the date — we have no internalDate for inaccessible
|
|
1059
|
+
// messages; pruneSkippedIds treats "0" as unknown and evicts last.
|
|
1060
|
+
skippedIds[ref.id] = "0";
|
|
1061
|
+
} else if (doc !== null && typeof doc === "object" && "kind" in doc) {
|
|
1062
|
+
// SkippedWithDate: empty or too-large. Gmail messages are immutable,
|
|
1063
|
+
// so record the id in skippedIds (Thread 2 fix) to prevent re-fetching
|
|
1064
|
+
// on every subsequent poll, AND update highWaterMs with the message's
|
|
1065
|
+
// internalDate so the watermark can advance past it when fully drained.
|
|
1066
|
+
// Store the internalDate so pruneSkippedIds can evict this entry once
|
|
1067
|
+
// the watermark advances past it. (Codex P2 PRRT_kwDORJXyws59z612)
|
|
1068
|
+
if (doc.kind === "empty") skippedEmpty++;
|
|
1069
|
+
else skippedTooLarge++;
|
|
1070
|
+
skippedIds[ref.id] =
|
|
1071
|
+
doc.internalDate.length > 0 ? doc.internalDate : "0";
|
|
1072
|
+
const skippedMs = Number(doc.internalDate);
|
|
1073
|
+
if (Number.isFinite(skippedMs) && skippedMs > highWaterMs) {
|
|
1074
|
+
highWaterMs = skippedMs;
|
|
1075
|
+
}
|
|
1076
|
+
} else if (doc !== null) {
|
|
1077
|
+
newDocs.push(doc as ConnectorDocument);
|
|
1078
|
+
// Track highest internalDate to advance watermark when fully drained.
|
|
1079
|
+
const successDoc = doc as ConnectorDocument;
|
|
1080
|
+
if (successDoc.source.externalRevision) {
|
|
1081
|
+
const msgMs = Number(successDoc.source.externalRevision);
|
|
1082
|
+
if (Number.isFinite(msgMs) && msgMs > highWaterMs) {
|
|
1083
|
+
highWaterMs = msgMs;
|
|
1084
|
+
}
|
|
1085
|
+
// Thread 1: record this id in seenIds so same-second re-queries
|
|
1086
|
+
// don't re-import it. seenIds maps id → internalDate ms string.
|
|
1087
|
+
seenIds[ref.id] = successDoc.source.externalRevision;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Resolve the next-page token from this page's response.
|
|
1093
|
+
const hasNextPage =
|
|
1094
|
+
typeof listPage.nextPageToken === "string" && listPage.nextPageToken.length > 0;
|
|
1095
|
+
const resolvedNextPageToken = hasNextPage ? listPage.nextPageToken : undefined;
|
|
1096
|
+
|
|
1097
|
+
if (capHitMidPage) {
|
|
1098
|
+
// Cap-hit mid-page fix (Codex P1 PRRT_kwDORJXyws59sh5I + Cursor
|
|
1099
|
+
// PRRT_kwDORJXyws59sji9): when the cap is hit while iterating this page,
|
|
1100
|
+
// persist the CURRENT page's token (the one used to fetch this page) so
|
|
1101
|
+
// the next poll re-fetches the same page and continues where we left off.
|
|
1102
|
+
// Messages already processed are in seenIds and will be skipped on
|
|
1103
|
+
// re-fetch. If pageToken is undefined we are on page 1, so the next poll
|
|
1104
|
+
// restarts from the beginning of the `after:` window — also correct.
|
|
1105
|
+
//
|
|
1106
|
+
// Previously we saved resolvedNextPageToken (the NEXT page's token),
|
|
1107
|
+
// which skipped all messages remaining on the current page — those
|
|
1108
|
+
// messages would never be processed.
|
|
1109
|
+
capHit = true;
|
|
1110
|
+
capHitPageToken = pageToken;
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// Continue to the next page if Gmail signals more results.
|
|
1115
|
+
if (resolvedNextPageToken !== undefined) {
|
|
1116
|
+
pageToken = resolvedNextPageToken;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// No nextPageToken — the list is fully drained for this `after:` window.
|
|
1121
|
+
listFullyDrained = true;
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// --- Watermark advancement ---
|
|
1126
|
+
//
|
|
1127
|
+
// Only advance when we fully drained the list (no cap hit, no premature
|
|
1128
|
+
// abort). Compare against the exact ms watermark (not the truncated
|
|
1129
|
+
// afterEpochSec * 1000) to prevent backward regression on clock skew.
|
|
1130
|
+
//
|
|
1131
|
+
// seenIds pruning (Codex P1 PRRT_kwDORJXyws59se73): after every pass, prune
|
|
1132
|
+
// seenIds via pruneSeenIds(seenIds, nextWatermarkMs). This replaces the
|
|
1133
|
+
// previous "clear seenIds when crossing a second boundary" approach, which
|
|
1134
|
+
// was incorrect — messages within the current watermark second can still
|
|
1135
|
+
// appear in the next `after:floor(watermarkMs/1000)` query, so clearing
|
|
1136
|
+
// seenIds caused re-imports. Instead we prune entries strictly below the
|
|
1137
|
+
// new watermark and enforce a hard size cap (SEEN_IDS_MAX / SEEN_IDS_RETAIN)
|
|
1138
|
+
// to bound cursor growth regardless of how many messages share the active
|
|
1139
|
+
// second window.
|
|
1140
|
+
//
|
|
1141
|
+
// Thread 2: pageToken in the next cursor is set only when the cap was hit
|
|
1142
|
+
// mid-page. It is cleared (not included) when the list is fully drained.
|
|
1143
|
+
let nextWatermarkMs: string;
|
|
1144
|
+
let nextSeenIds: Record<string, string>;
|
|
1145
|
+
let nextSkippedIds: Record<string, string>;
|
|
1146
|
+
let nextPageToken: string | undefined;
|
|
1147
|
+
|
|
1148
|
+
if (listFullyDrained && !capHit && highWaterMs > currentWatermarkMs) {
|
|
1149
|
+
nextWatermarkMs = String(highWaterMs);
|
|
1150
|
+
// Prune seenIds to new watermark and enforce size cap.
|
|
1151
|
+
nextSeenIds = pruneSeenIds(seenIds, highWaterMs);
|
|
1152
|
+
// Prune skippedIds to new watermark. (Codex P2 PRRT_kwDORJXyws59z612)
|
|
1153
|
+
nextSkippedIds = pruneSkippedIds(skippedIds, highWaterMs);
|
|
1154
|
+
// Window fully drained — clear the page token.
|
|
1155
|
+
nextPageToken = undefined;
|
|
1156
|
+
} else if (capHit) {
|
|
1157
|
+
// Cap hit mid-page: keep watermark; prune seenIds to current watermark
|
|
1158
|
+
// and enforce size cap; persist the current page token (Thread 2 / fix
|
|
1159
|
+
// PRRT_kwDORJXyws59sh5I).
|
|
1160
|
+
nextWatermarkMs = cursorPayload.watermarkMs;
|
|
1161
|
+
nextSeenIds = pruneSeenIds(seenIds, currentWatermarkMs);
|
|
1162
|
+
nextSkippedIds = pruneSkippedIds(skippedIds, currentWatermarkMs);
|
|
1163
|
+
nextPageToken = capHitPageToken;
|
|
1164
|
+
} else {
|
|
1165
|
+
// Watermark unchanged (list drained but no new messages, or aborted) —
|
|
1166
|
+
// keep exact ms string; prune seenIds to current watermark; no page token.
|
|
1167
|
+
nextWatermarkMs = cursorPayload.watermarkMs;
|
|
1168
|
+
nextSeenIds = pruneSeenIds(seenIds, currentWatermarkMs);
|
|
1169
|
+
nextSkippedIds = pruneSkippedIds(skippedIds, currentWatermarkMs);
|
|
1170
|
+
nextPageToken = undefined;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const nextCursor = makeCursor({
|
|
1174
|
+
watermarkMs: nextWatermarkMs,
|
|
1175
|
+
skippedIds: nextSkippedIds,
|
|
1176
|
+
seenIds: nextSeenIds,
|
|
1177
|
+
...(nextPageToken !== undefined ? { pageToken: nextPageToken } : {}),
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
return {
|
|
1181
|
+
newDocs,
|
|
1182
|
+
nextCursor,
|
|
1183
|
+
skippedInaccessible,
|
|
1184
|
+
skippedEmpty,
|
|
1185
|
+
skippedTooLarge,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* Build the Gmail query string combining the `after:` watermark filter with
|
|
1191
|
+
* the operator-configured `query`. The `after:` operator takes epoch seconds.
|
|
1192
|
+
*/
|
|
1193
|
+
function buildListQuery(afterEpochSec: number, userQuery: string): string {
|
|
1194
|
+
const parts: string[] = [];
|
|
1195
|
+
if (afterEpochSec > 0) {
|
|
1196
|
+
parts.push(`after:${afterEpochSec}`);
|
|
1197
|
+
}
|
|
1198
|
+
const trimmedUser = userQuery.trim();
|
|
1199
|
+
if (trimmedUser.length > 0) {
|
|
1200
|
+
parts.push(trimmedUser);
|
|
1201
|
+
}
|
|
1202
|
+
return parts.join(" ");
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// ---------------------------------------------------------------------------
|
|
1206
|
+
// Per-message document fetch
|
|
1207
|
+
// ---------------------------------------------------------------------------
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* Tagged result for skipped messages that have an `internalDate` available.
|
|
1211
|
+
* The caller uses the date to advance the watermark past immutable messages
|
|
1212
|
+
* (empty or too-large) that would otherwise stall the watermark forever
|
|
1213
|
+
* (Cursor Medium review: empty/too-large messages permanently stall watermark).
|
|
1214
|
+
*/
|
|
1215
|
+
type SkippedWithDate = { kind: "empty" | "too-large"; internalDate: string };
|
|
1216
|
+
|
|
1217
|
+
async function fetchMessageDocument(
|
|
1218
|
+
fetchFn: GmailFetchFn,
|
|
1219
|
+
accessToken: string,
|
|
1220
|
+
config: GmailConnectorConfig,
|
|
1221
|
+
messageId: string,
|
|
1222
|
+
fetchedAt: string,
|
|
1223
|
+
signal: AbortSignal | undefined,
|
|
1224
|
+
): Promise<ConnectorDocument | SkippedWithDate | "inaccessible" | null> {
|
|
1225
|
+
throwIfAborted(signal);
|
|
1226
|
+
|
|
1227
|
+
let message: GmailMessage;
|
|
1228
|
+
try {
|
|
1229
|
+
const path = `/users/${encodeURIComponent(config.userId)}/messages/${encodeURIComponent(messageId)}?format=full`;
|
|
1230
|
+
const data = await gmailGet(fetchFn, accessToken, path, signal);
|
|
1231
|
+
message = data as GmailMessage;
|
|
1232
|
+
} catch (err) {
|
|
1233
|
+
if (isTransientGmailError(err)) {
|
|
1234
|
+
// Transient: re-throw to stop the pass without advancing the cursor.
|
|
1235
|
+
throw err;
|
|
1236
|
+
}
|
|
1237
|
+
// 401 Unauthorized on a per-message fetch is also transient: the access
|
|
1238
|
+
// token may have expired mid-pass. Re-throwing prevents the message from
|
|
1239
|
+
// being permanently blacklisted in skippedIds when credentials are
|
|
1240
|
+
// temporarily invalid. The next poll will re-fetch a fresh token and retry.
|
|
1241
|
+
// (Codex P1 PRRT_kwDORJXyws59z61w)
|
|
1242
|
+
if (
|
|
1243
|
+
err !== null &&
|
|
1244
|
+
typeof err === "object" &&
|
|
1245
|
+
(err as { gmailStatus?: unknown }).gmailStatus === 401
|
|
1246
|
+
) {
|
|
1247
|
+
throw err;
|
|
1248
|
+
}
|
|
1249
|
+
// Terminal (404 / 403 / 400): skip this message.
|
|
1250
|
+
return "inaccessible";
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
const internalDate = message.internalDate ?? "";
|
|
1254
|
+
|
|
1255
|
+
// Extract body text.
|
|
1256
|
+
const body = message.payload ? extractBodyFromPart(message.payload) : "";
|
|
1257
|
+
|
|
1258
|
+
if (typeof body !== "string" || body.trim().length === 0) {
|
|
1259
|
+
// Return the internalDate so the caller can advance the watermark past
|
|
1260
|
+
// this immutable empty message (it will never have content).
|
|
1261
|
+
return { kind: "empty", internalDate };
|
|
1262
|
+
}
|
|
1263
|
+
if (body.length > MAX_TEXT_BYTES) {
|
|
1264
|
+
// Same for too-large: the message is immutable; record its date.
|
|
1265
|
+
return { kind: "too-large", internalDate };
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
const subject = extractSubject(message);
|
|
1269
|
+
|
|
1270
|
+
return {
|
|
1271
|
+
id: messageId,
|
|
1272
|
+
title: subject,
|
|
1273
|
+
content: body,
|
|
1274
|
+
source: {
|
|
1275
|
+
connector: GMAIL_CONNECTOR_ID,
|
|
1276
|
+
externalId: messageId,
|
|
1277
|
+
// Store internalDate (epoch ms string) as the revision so downstream
|
|
1278
|
+
// dedup can identify repeat fetches after cursor rewind.
|
|
1279
|
+
externalRevision: internalDate.length > 0 ? internalDate : undefined,
|
|
1280
|
+
fetchedAt,
|
|
1281
|
+
},
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// ---------------------------------------------------------------------------
|
|
1286
|
+
// Watermark helpers (exported for tests)
|
|
1287
|
+
// ---------------------------------------------------------------------------
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Convert an `internalDate` epoch-ms string to an ISO 8601 timestamp.
|
|
1291
|
+
* Exported for test assertions.
|
|
1292
|
+
*/
|
|
1293
|
+
export { internalDateToIso, internalDateToEpochSeconds, buildListQuery };
|