@remnic/core 1.1.11 → 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/README.md +3 -3
- package/dist/access-cli.d.ts +2 -1
- package/dist/access-cli.js +293 -104
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +31 -62
- package/dist/access-http.js +53 -35
- package/dist/access-mcp.d.ts +31 -8
- package/dist/access-mcp.js +45 -34
- package/dist/access-schema.d.ts +197 -14
- package/dist/access-schema.js +16 -5
- package/dist/access-service-DcCDmNYC.d.ts +1542 -0
- package/dist/access-service.d.ts +30 -9
- package/dist/access-service.js +42 -32
- package/dist/action-confidence.d.ts +83 -0
- package/dist/action-confidence.js +22 -0
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.d.ts +1 -1
- 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/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +23 -6
- package/dist/boxes.d.ts +7 -0
- package/dist/boxes.js +1 -1
- package/dist/briefing.d.ts +5 -3
- package/dist/briefing.js +10 -7
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-surprise-report.js +1 -1
- package/dist/buffer.d.ts +18 -4
- package/dist/buffer.js +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/calibration.js +6 -6
- 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 +9 -29
- 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 +2 -2
- package/dist/causal-consolidation.js +28 -17
- 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-4RA3C3EV.js +60 -0
- package/dist/chunk-4RA3C3EV.js.map +1 -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-5NXIJZFX.js +180 -0
- package/dist/chunk-5NXIJZFX.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-6NKAQ74D.js +2237 -0
- package/dist/chunk-6NKAQ74D.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-AC5LO7IU.js +308 -0
- package/dist/chunk-AC5LO7IU.js.map +1 -0
- package/dist/chunk-AGZQD76C.js +201 -0
- package/dist/chunk-AGZQD76C.js.map +1 -0
- package/dist/chunk-AH2JUU6X.js +336 -0
- package/dist/chunk-AH2JUU6X.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-C5BCH4ZS.js +317 -0
- package/dist/chunk-C5BCH4ZS.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-DB5A3NHS.js +906 -0
- package/dist/chunk-DB5A3NHS.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-EDTHC6UD.js.map +1 -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-FBYESMQ2.js.map +1 -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-MGKYQQYF.js.map +1 -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-OAZ5MFUB.js +4124 -0
- package/dist/chunk-OAZ5MFUB.js.map +1 -0
- package/dist/chunk-OIGNEXKZ.js +237 -0
- package/dist/chunk-OIGNEXKZ.js.map +1 -0
- package/dist/chunk-OZKZ2TRP.js +3729 -0
- package/dist/chunk-OZKZ2TRP.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-PD6O7AXF.js +110 -0
- package/dist/chunk-PD6O7AXF.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-PYPOFEMK.js +294 -0
- package/dist/chunk-PYPOFEMK.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-QDZ2RLEC.js +908 -0
- package/dist/chunk-QDZ2RLEC.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-RK6F44Y6.js +84 -0
- package/dist/chunk-RK6F44Y6.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-SOAU2OE2.js +125 -0
- package/dist/chunk-SOAU2OE2.js.map +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-V5OCT34X.js.map +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-VWT3F4IV.js +2161 -0
- package/dist/chunk-VWT3F4IV.js.map +1 -0
- package/dist/chunk-W3LR522O.js +2296 -0
- package/dist/chunk-W3LR522O.js.map +1 -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 +42 -10
- package/dist/cli.js +121 -58
- package/dist/codex-cli-fallback.d.ts +1 -0
- package/dist/codex-cli-fallback.js +1 -1
- 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/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +5 -3
- 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 +4 -2
- package/dist/consolidation-undo.d.ts +4 -2
- 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/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer-wiring.js +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- 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 +9 -6
- package/dist/entity-schema.d.ts +1 -1
- package/dist/evals.js +1 -1
- package/dist/event-order-recall.d.ts +17 -0
- package/dist/event-order-recall.js +11 -0
- package/dist/event-order-recall.js.map +1 -0
- package/dist/evidence-pack.d.ts +3 -1
- package/dist/evidence-pack.js +5 -3
- package/dist/explicit-capture.d.ts +23 -6
- package/dist/explicit-capture.js +2 -2
- package/dist/explicit-cue-recall.d.ts +4 -1
- package/dist/explicit-cue-recall.js +4 -2
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge-training.js +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +11 -10
- package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
- package/dist/fallback-llm.d.ts +4 -1
- package/dist/fallback-llm.js +6 -6
- package/dist/focused-list-recall.d.ts +17 -0
- package/dist/focused-list-recall.js +11 -0
- package/dist/focused-list-recall.js.map +1 -0
- package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index-DJ9QWMw-.d.ts +35 -0
- package/dist/index.d.ts +107 -715
- package/dist/index.js +657 -2611
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/intent.js +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/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/live-connectors-runner.js +5 -5
- package/dist/local-llm.d.ts +8 -4
- 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 +124 -0
- package/dist/mcp-memory-inspector-app.js +20 -0
- package/dist/mcp-memory-inspector-app.js.map +1 -0
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +108 -3
- package/dist/memory-projection-store.js +2 -1
- package/dist/memory-provenance.d.ts +57 -0
- package/dist/memory-provenance.js +13 -0
- package/dist/memory-provenance.js.map +1 -0
- 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/models-json.d.ts +1 -1
- 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/native-knowledge.d.ts +1 -1
- 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.d.ts +1 -1
- package/dist/objective-state-writers.js +2 -2
- package/dist/operator-toolkit.d.ts +4 -2
- package/dist/operator-toolkit.js +35 -17
- 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 +24 -7
- package/dist/orchestrator.js +107 -65
- package/dist/path-MR5JPYOP.js +9 -0
- package/dist/path-MR5JPYOP.js.map +1 -0
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +2 -2
- package/dist/qmd.d.ts +103 -4
- package/dist/qmd.js +23 -5
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +3 -1
- package/dist/recall-explain-renderer.js +5 -3
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +3 -1
- package/dist/recall-xray-cli.d.ts +3 -1
- package/dist/recall-xray-cli.js +6 -4
- package/dist/recall-xray-renderer.d.ts +3 -1
- package/dist/recall-xray-renderer.js +5 -3
- package/dist/recall-xray.d.ts +8 -1
- package/dist/recall-xray.js +4 -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-auth-token.d.ts +1 -1
- package/dist/resolve-provider-secret.js +2 -2
- package/dist/response-guidance-recall.d.ts +18 -0
- package/dist/response-guidance-recall.js +11 -0
- package/dist/response-guidance-recall.js.map +1 -0
- package/dist/resume-bundles.js +7 -5
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.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/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 +3 -3
- package/dist/semantic-consolidation.js +15 -8
- package/dist/semantic-rule-promotion.js +9 -6
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +10 -7
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- 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/signal.d.ts +1 -1
- 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 +6 -2
- package/dist/storage.js +8 -5
- package/dist/summarizer.d.ts +6 -1
- package/dist/summarizer.js +11 -10
- package/dist/summary-snapshot.d.ts +1 -1
- 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/targeted-fact-recall.d.ts +17 -0
- package/dist/targeted-fact-recall.js +11 -0
- package/dist/targeted-fact-recall.js.map +1 -0
- package/dist/telemetry-transcript.d.ts +7 -0
- package/dist/telemetry-transcript.js +16 -0
- package/dist/telemetry-transcript.js.map +1 -0
- package/dist/temporal-supersession.d.ts +4 -2
- package/dist/temporal-supersession.js +2 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +6 -1
- package/dist/threading.js +2 -1
- package/dist/tier-migration.d.ts +5 -3
- package/dist/tier-routing.d.ts +1 -1
- package/dist/tokens.js +2 -2
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +16 -2
- 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/trust-zones.d.ts +3 -2
- package/dist/trust-zones.js +1 -1
- package/dist/types.d.ts +88 -3
- package/dist/types.js +1 -1
- package/dist/user-model.d.ts +37 -0
- package/dist/user-model.js +28 -0
- package/dist/user-model.js.map +1 -0
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +11 -8
- 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-BkXt3di1.d.ts +0 -2039
- package/dist/capsule-crypto-SJS5VVAP.js +0 -18
- package/dist/capsule-export-LLEVB2RG.js +0 -17
- package/dist/capsule-import-UW45R2MZ.js +0 -16
- package/dist/capsule-merge-DI7PNQ2H.js +0 -189
- package/dist/chunk-2LGMW3DJ.js +0 -111
- package/dist/chunk-2YMTO4ZJ.js +0 -265
- package/dist/chunk-2YMTO4ZJ.js.map +0 -1
- package/dist/chunk-363MWCD3.js +0 -9683
- package/dist/chunk-363MWCD3.js.map +0 -1
- package/dist/chunk-36CTNQY7.js +0 -1554
- package/dist/chunk-36CTNQY7.js.map +0 -1
- package/dist/chunk-457A4P3L.js +0 -119
- package/dist/chunk-457A4P3L.js.map +0 -1
- package/dist/chunk-4DXC6HQQ.js +0 -1837
- package/dist/chunk-4DXC6HQQ.js.map +0 -1
- package/dist/chunk-4IS4SXIQ.js +0 -2040
- package/dist/chunk-57QNCUEZ.js +0 -1914
- package/dist/chunk-57QNCUEZ.js.map +0 -1
- package/dist/chunk-6AUUAZEX.js +0 -150
- package/dist/chunk-6AUUAZEX.js.map +0 -1
- package/dist/chunk-6TBWYBJ3.js +0 -236
- package/dist/chunk-6XA7UN4Z.js +0 -135
- package/dist/chunk-6Z6UH6TK.js +0 -2129
- package/dist/chunk-6Z6UH6TK.js.map +0 -1
- package/dist/chunk-74EMIVE4.js +0 -329
- package/dist/chunk-74EMIVE4.js.map +0 -1
- package/dist/chunk-74WWN7ZW.js +0 -82
- package/dist/chunk-74WWN7ZW.js.map +0 -1
- package/dist/chunk-767ODGE6.js +0 -183
- package/dist/chunk-A4ACKWIW.js +0 -289
- package/dist/chunk-A4ACKWIW.js.map +0 -1
- package/dist/chunk-ASAITVLA.js +0 -64
- package/dist/chunk-ASAITVLA.js.map +0 -1
- package/dist/chunk-C5HUWVH2.js +0 -891
- package/dist/chunk-C5HUWVH2.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-E6K4NIEU.js +0 -747
- package/dist/chunk-E6K4NIEU.js.map +0 -1
- package/dist/chunk-EEQLFRUM.js +0 -89
- package/dist/chunk-EQINRHYR.js +0 -672
- package/dist/chunk-EQINRHYR.js.map +0 -1
- package/dist/chunk-ETOW6ACV.js +0 -158
- package/dist/chunk-ETOW6ACV.js.map +0 -1
- package/dist/chunk-EYNQTST2.js +0 -721
- 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-GGD5W7TB.js +0 -105
- package/dist/chunk-GGD5W7TB.js.map +0 -1
- package/dist/chunk-GVPWB7EY.js +0 -390
- package/dist/chunk-GVPWB7EY.js.map +0 -1
- package/dist/chunk-HJYHRE4S.js +0 -647
- package/dist/chunk-I6BQZSML.js +0 -1451
- package/dist/chunk-I6BQZSML.js.map +0 -1
- package/dist/chunk-IBX3VFOM.js +0 -446
- package/dist/chunk-IBX3VFOM.js.map +0 -1
- 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-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-KBYWQWSB.js +0 -271
- package/dist/chunk-KUHRUM6B.js +0 -14397
- package/dist/chunk-KUHRUM6B.js.map +0 -1
- package/dist/chunk-KWBPHZUU.js +0 -83
- package/dist/chunk-KWBPHZUU.js.map +0 -1
- package/dist/chunk-LIO5X3CM.js +0 -596
- package/dist/chunk-MARWOCVP.js +0 -48
- package/dist/chunk-MCC6KDQF.js +0 -5095
- package/dist/chunk-MCC6KDQF.js.map +0 -1
- package/dist/chunk-N5AKDXAI.js +0 -74
- package/dist/chunk-NN3LPQ5D.js +0 -936
- package/dist/chunk-NN3LPQ5D.js.map +0 -1
- package/dist/chunk-O4XJUPSF.js +0 -533
- package/dist/chunk-O4XJUPSF.js.map +0 -1
- 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-P73JTV34.js +0 -275
- package/dist/chunk-P73JTV34.js.map +0 -1
- package/dist/chunk-P77UEOU2.js +0 -1521
- package/dist/chunk-P77UEOU2.js.map +0 -1
- package/dist/chunk-PB5KW5PL.js +0 -118
- package/dist/chunk-PHNGXFQ6.js +0 -623
- package/dist/chunk-PHNGXFQ6.js.map +0 -1
- package/dist/chunk-QIGOEM65.js +0 -228
- package/dist/chunk-RXTFCYQF.js +0 -108
- package/dist/chunk-S2JJBLJG.js +0 -2101
- package/dist/chunk-S2JJBLJG.js.map +0 -1
- package/dist/chunk-S3IP6R6K.js +0 -219
- package/dist/chunk-S3IP6R6K.js.map +0 -1
- package/dist/chunk-SRBJUAMP.js +0 -403
- package/dist/chunk-SRBJUAMP.js.map +0 -1
- package/dist/chunk-URB2WSKZ.js +0 -350
- package/dist/chunk-URB2WSKZ.js.map +0 -1
- package/dist/chunk-VQXK37XA.js +0 -26
- package/dist/chunk-VQXK37XA.js.map +0 -1
- package/dist/chunk-VTU2B4VF.js +0 -146
- package/dist/chunk-VTU2B4VF.js.map +0 -1
- package/dist/chunk-VX2IUQFE.js +0 -613
- package/dist/chunk-VX2IUQFE.js.map +0 -1
- package/dist/chunk-WGK4VHGP.js +0 -4292
- package/dist/chunk-WGK4VHGP.js.map +0 -1
- package/dist/chunk-WTFWLUSX.js +0 -827
- package/dist/chunk-WTFWLUSX.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-Y5KDIOKF.js +0 -2403
- package/dist/chunk-Y5KDIOKF.js.map +0 -1
- 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-Z5S5HNGY.js +0 -2280
- package/dist/chunk-Z5S5HNGY.js.map +0 -1
- package/dist/chunk-ZL4S7ARC.js +0 -53
- package/dist/chunk-ZTSE2ZJ6.js +0 -190
- package/dist/chunk-ZTSE2ZJ6.js.map +0 -1
- package/dist/cli-Cvy2SNhF.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-3Z6YW7YA.js +0 -413
- package/dist/contradiction-scan-3Z6YW7YA.js.map +0 -1
- package/dist/engine-FOC3IJLA.js +0 -28
- package/dist/fs-utils-IRVUFB6G.js +0 -30
- package/dist/graph-edge-decay-PWB63GRE.js +0 -207
- package/dist/index-1qIcnbG1.d.ts +0 -34
- package/dist/memory-governance-F3QOJGEY.js +0 -37
- package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
- package/dist/orchestrator-AOQMo7QI.d.ts +0 -1784
- 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 → action-confidence.js.map} +0 -0
- /package/dist/{capsule-export-LLEVB2RG.js.map → adapters/claude-code.js.map} +0 -0
- /package/dist/{capsule-import-UW45R2MZ.js.map → adapters/codex.js.map} +0 -0
- /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/hermes.js.map} +0 -0
- /package/dist/{engine-FOC3IJLA.js.map → adapters/index.js.map} +0 -0
- /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/registry.js.map} +0 -0
- /package/dist/{memory-governance-F3QOJGEY.js.map → adapters/replit.js.map} +0 -0
- /package/dist/{path-RMTY5Y5A.js.map → adapters/types.js.map} +0 -0
- /package/dist/{secure-store-4R2GSO7S.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-KBYWQWSB.js.map → chunk-4CRG46BG.js.map} +0 -0
- /package/dist/{chunk-LIO5X3CM.js.map → chunk-7IASACLB.js.map} +0 -0
- /package/dist/{chunk-EYNQTST2.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
- /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
- /package/dist/{chunk-QIGOEM65.js.map → chunk-GGKRUQOO.js.map} +0 -0
- /package/dist/{chunk-HJYHRE4S.js.map → chunk-GL6I6MEQ.js.map} +0 -0
- /package/dist/{state-store-3EH7HYIN.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-PB5KW5PL.js.map → chunk-MC26UJIM.js.map} +0 -0
- /package/dist/{chunk-ZL4S7ARC.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-RXTFCYQF.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/{types-V3FJ26TF.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/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
- /package/dist/{chunk-6XA7UN4Z.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,1706 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
GMAIL_CONNECTOR_ID,
|
|
6
|
+
GMAIL_CURSOR_KIND,
|
|
7
|
+
GMAIL_DEFAULT_POLL_INTERVAL_MS,
|
|
8
|
+
MAX_MESSAGES_PER_PASS,
|
|
9
|
+
SEEN_IDS_MAX,
|
|
10
|
+
SEEN_IDS_RETAIN,
|
|
11
|
+
buildListQuery,
|
|
12
|
+
createGmailConnector,
|
|
13
|
+
internalDateToEpochSeconds,
|
|
14
|
+
internalDateToIso,
|
|
15
|
+
isTransientGmailError,
|
|
16
|
+
pruneSeenIds,
|
|
17
|
+
validateGmailConfig,
|
|
18
|
+
type GmailFetchFn,
|
|
19
|
+
type GmailMessage,
|
|
20
|
+
type GmailMessageRef,
|
|
21
|
+
type GmailSyncResult,
|
|
22
|
+
} from "./gmail.js";
|
|
23
|
+
import { isTransientHttpError } from "./transient-errors.js";
|
|
24
|
+
import type { ConnectorCursor } from "./framework.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Tests for the Gmail connector (#683 PR 4/6). All Gmail API calls are
|
|
28
|
+
* stubbed via the `fetchFn` test hook — the test suite never touches the
|
|
29
|
+
* network.
|
|
30
|
+
*
|
|
31
|
+
* Per CLAUDE.md privacy rules: no real credentials, no real message ids,
|
|
32
|
+
* no real email addresses. All inputs are obviously-synthetic strings.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Synthetic test data
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
const SYNTHETIC_CREDS = Object.freeze({
|
|
40
|
+
clientId: "synthetic-gmail-client-id.apps.googleusercontent.com",
|
|
41
|
+
clientSecret: "synthetic-gmail-client-secret-DO-NOT-USE",
|
|
42
|
+
refreshToken: "synthetic-gmail-refresh-token-DO-NOT-USE",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const SYNTHETIC_ACCESS_TOKEN = "synthetic-access-token-DO-NOT-USE";
|
|
46
|
+
|
|
47
|
+
/** A synthetic internalDate (epoch ms as string). */
|
|
48
|
+
const T1 = "1745000000000"; // ~2025-04-14
|
|
49
|
+
const T2 = "1745001000000"; // ~1000 s later
|
|
50
|
+
const T3 = "1745002000000"; // ~2000 s later
|
|
51
|
+
|
|
52
|
+
function makeMessageRef(id: string): GmailMessageRef {
|
|
53
|
+
return { id, threadId: `thread-${id}` };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function makeMessage(
|
|
57
|
+
id: string,
|
|
58
|
+
internalDate: string,
|
|
59
|
+
bodyText: string,
|
|
60
|
+
subject?: string,
|
|
61
|
+
): GmailMessage {
|
|
62
|
+
const headers = subject
|
|
63
|
+
? [{ name: "Subject", value: subject }]
|
|
64
|
+
: [];
|
|
65
|
+
return {
|
|
66
|
+
id,
|
|
67
|
+
threadId: `thread-${id}`,
|
|
68
|
+
internalDate,
|
|
69
|
+
payload: {
|
|
70
|
+
mimeType: "text/plain",
|
|
71
|
+
headers,
|
|
72
|
+
body: {
|
|
73
|
+
data: Buffer.from(bodyText, "utf-8").toString("base64url"),
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Minimal fetch builder
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
type FetchCall = { url: string; method: string };
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build a `GmailFetchFn` stub. Handlers are matched first-to-last.
|
|
87
|
+
* Each handler specifies a URL substring match and a factory that returns
|
|
88
|
+
* `{ status, data }`.
|
|
89
|
+
*/
|
|
90
|
+
function makeFetch(
|
|
91
|
+
handlers: Array<{
|
|
92
|
+
match: (url: string, method: string) => boolean;
|
|
93
|
+
respond: (url: string, body: string | undefined) => { status: number; data: unknown };
|
|
94
|
+
}>,
|
|
95
|
+
calls?: FetchCall[],
|
|
96
|
+
): GmailFetchFn {
|
|
97
|
+
return async (url, init) => {
|
|
98
|
+
if (calls) calls.push({ url, method: init.method });
|
|
99
|
+
for (const handler of handlers) {
|
|
100
|
+
if (handler.match(url, init.method)) {
|
|
101
|
+
const { status, data } = handler.respond(url, init.body);
|
|
102
|
+
return {
|
|
103
|
+
ok: status >= 200 && status < 300,
|
|
104
|
+
status,
|
|
105
|
+
json: async () => data,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`fetch stub: no handler for ${init.method} ${url}`);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** OAuth2 token exchange handler — always succeeds. */
|
|
114
|
+
function tokenHandler(): {
|
|
115
|
+
match: (url: string, method: string) => boolean;
|
|
116
|
+
respond: (url: string, body: string | undefined) => { status: number; data: unknown };
|
|
117
|
+
} {
|
|
118
|
+
return {
|
|
119
|
+
match: (url) => url.startsWith("https://oauth2.googleapis.com/"),
|
|
120
|
+
respond: () => ({
|
|
121
|
+
status: 200,
|
|
122
|
+
data: { access_token: SYNTHETIC_ACCESS_TOKEN, token_type: "Bearer", expires_in: 3600 },
|
|
123
|
+
}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** messages.list handler returning the given refs. */
|
|
128
|
+
function listHandler(
|
|
129
|
+
refs: GmailMessageRef[],
|
|
130
|
+
nextPageToken?: string,
|
|
131
|
+
): {
|
|
132
|
+
match: (url: string, method: string) => boolean;
|
|
133
|
+
respond: (url: string, body: string | undefined) => { status: number; data: unknown };
|
|
134
|
+
} {
|
|
135
|
+
return {
|
|
136
|
+
match: (url) => url.includes("/messages?") || url.includes("/messages&") || (url.includes("/messages") && !url.match(/\/messages\/[^?]/)),
|
|
137
|
+
respond: () => ({
|
|
138
|
+
status: 200,
|
|
139
|
+
data: nextPageToken
|
|
140
|
+
? { messages: refs, nextPageToken }
|
|
141
|
+
: { messages: refs },
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** messages.get handler returning the given message map. */
|
|
147
|
+
function getHandler(
|
|
148
|
+
messages: Record<string, GmailMessage>,
|
|
149
|
+
statusOverride?: Record<string, number>,
|
|
150
|
+
): {
|
|
151
|
+
match: (url: string, method: string) => boolean;
|
|
152
|
+
respond: (url: string, body: string | undefined) => { status: number; data: unknown };
|
|
153
|
+
} {
|
|
154
|
+
return {
|
|
155
|
+
match: (url) => /\/messages\/[^?]+/.test(url) && url.includes("format=full"),
|
|
156
|
+
respond: (url) => {
|
|
157
|
+
// Extract message id from URL path.
|
|
158
|
+
const m = url.match(/\/messages\/([^?]+)/);
|
|
159
|
+
const id = m ? decodeURIComponent(m[1]) : "";
|
|
160
|
+
const statusCode = statusOverride?.[id] ?? 200;
|
|
161
|
+
if (statusCode !== 200) {
|
|
162
|
+
return {
|
|
163
|
+
status: statusCode,
|
|
164
|
+
data: { error: { message: `HTTP ${statusCode}`, code: statusCode } },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const msg = messages[id];
|
|
168
|
+
if (!msg) {
|
|
169
|
+
return {
|
|
170
|
+
status: 404,
|
|
171
|
+
data: { error: { message: "not found", code: 404 } },
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return { status: 200, data: msg };
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Config validation
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
test("validateGmailConfig accepts a minimal valid config", () => {
|
|
184
|
+
const cfg = validateGmailConfig({ ...SYNTHETIC_CREDS });
|
|
185
|
+
assert.equal(cfg.clientId, SYNTHETIC_CREDS.clientId);
|
|
186
|
+
assert.equal(cfg.clientSecret, SYNTHETIC_CREDS.clientSecret);
|
|
187
|
+
assert.equal(cfg.refreshToken, SYNTHETIC_CREDS.refreshToken);
|
|
188
|
+
assert.equal(cfg.userId, "me");
|
|
189
|
+
assert.equal(cfg.query, "in:inbox");
|
|
190
|
+
assert.equal(cfg.pollIntervalMs, GMAIL_DEFAULT_POLL_INTERVAL_MS);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("validateGmailConfig rejects non-object input", () => {
|
|
194
|
+
assert.throws(() => validateGmailConfig(null), /must be an object/);
|
|
195
|
+
assert.throws(() => validateGmailConfig([]), /must be an object/);
|
|
196
|
+
assert.throws(() => validateGmailConfig("nope"), /must be an object/);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("validateGmailConfig rejects missing or empty credentials", () => {
|
|
200
|
+
assert.throws(
|
|
201
|
+
() => validateGmailConfig({ clientSecret: "x", refreshToken: "y" }),
|
|
202
|
+
/clientId/,
|
|
203
|
+
);
|
|
204
|
+
assert.throws(
|
|
205
|
+
() => validateGmailConfig({ clientId: "x", refreshToken: "y" }),
|
|
206
|
+
/clientSecret/,
|
|
207
|
+
);
|
|
208
|
+
assert.throws(
|
|
209
|
+
() => validateGmailConfig({ clientId: "x", clientSecret: "y" }),
|
|
210
|
+
/refreshToken/,
|
|
211
|
+
);
|
|
212
|
+
assert.throws(
|
|
213
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, clientId: " " }),
|
|
214
|
+
/clientId.*non-empty/,
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("validateGmailConfig rejects malformed pollIntervalMs", () => {
|
|
219
|
+
assert.throws(
|
|
220
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, pollIntervalMs: "300000" }),
|
|
221
|
+
/pollIntervalMs/,
|
|
222
|
+
);
|
|
223
|
+
assert.throws(
|
|
224
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, pollIntervalMs: 50 }),
|
|
225
|
+
/≥1000/,
|
|
226
|
+
);
|
|
227
|
+
assert.throws(
|
|
228
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, pollIntervalMs: 25 * 60 * 60 * 1000 }),
|
|
229
|
+
/≤/,
|
|
230
|
+
);
|
|
231
|
+
assert.throws(
|
|
232
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, pollIntervalMs: 3000.5 }),
|
|
233
|
+
/integer/,
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("validateGmailConfig accepts custom userId, query, and pollIntervalMs", () => {
|
|
238
|
+
const cfg = validateGmailConfig({
|
|
239
|
+
...SYNTHETIC_CREDS,
|
|
240
|
+
userId: "user@example.com",
|
|
241
|
+
query: "label:work",
|
|
242
|
+
pollIntervalMs: 60_000,
|
|
243
|
+
});
|
|
244
|
+
assert.equal(cfg.userId, "user@example.com");
|
|
245
|
+
assert.equal(cfg.query, "label:work");
|
|
246
|
+
assert.equal(cfg.pollIntervalMs, 60_000);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("validateGmailConfig rejects empty userId string", () => {
|
|
250
|
+
assert.throws(
|
|
251
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, userId: " " }),
|
|
252
|
+
/userId.*non-empty/,
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Connector identity
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
test("createGmailConnector exposes the documented id and display name", () => {
|
|
261
|
+
const connector = createGmailConnector({
|
|
262
|
+
fetchFn: makeFetch([tokenHandler()]),
|
|
263
|
+
});
|
|
264
|
+
assert.equal(connector.id, GMAIL_CONNECTOR_ID);
|
|
265
|
+
assert.equal(connector.displayName, "Gmail");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// First-sync bootstrap behavior
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
test("first sync (cursor=null) returns no docs and seeds the cursor with now", async () => {
|
|
273
|
+
const before = Date.now();
|
|
274
|
+
const connector = createGmailConnector({
|
|
275
|
+
fetchFn: makeFetch([tokenHandler()]),
|
|
276
|
+
});
|
|
277
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
278
|
+
|
|
279
|
+
const result = await connector.syncIncremental({ cursor: null, config });
|
|
280
|
+
|
|
281
|
+
const after = Date.now();
|
|
282
|
+
assert.deepEqual(result.newDocs, []);
|
|
283
|
+
assert.equal(result.nextCursor.kind, GMAIL_CURSOR_KIND);
|
|
284
|
+
|
|
285
|
+
// Cursor must store watermarkMs (epoch-ms string), NOT watermarkIso (Thread 1).
|
|
286
|
+
const payload = JSON.parse(result.nextCursor.value) as { watermarkMs: string; watermarkIso?: string };
|
|
287
|
+
assert.ok(typeof payload.watermarkMs === "string", "cursor must have watermarkMs");
|
|
288
|
+
assert.equal(payload.watermarkIso, undefined, "cursor must NOT have watermarkIso (use watermarkMs)");
|
|
289
|
+
const watermarkMs = Number(payload.watermarkMs);
|
|
290
|
+
assert.ok(watermarkMs >= before, "watermark should be >= before");
|
|
291
|
+
assert.ok(watermarkMs <= after + 100, "watermark should be <= after");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// pollOnce — basic happy-path incremental sync
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
test("incremental sync emits ConnectorDocument entries for new messages", async () => {
|
|
299
|
+
const msg1 = makeMessage("msg-id-001", T1, "Hello world from message 001", "Subject One");
|
|
300
|
+
const msg2 = makeMessage("msg-id-002", T2, "Hello world from message 002", "Subject Two");
|
|
301
|
+
|
|
302
|
+
const fetchFn = makeFetch([
|
|
303
|
+
tokenHandler(),
|
|
304
|
+
listHandler([makeMessageRef("msg-id-001"), makeMessageRef("msg-id-002")]),
|
|
305
|
+
getHandler({ "msg-id-001": msg1, "msg-id-002": msg2 }),
|
|
306
|
+
]);
|
|
307
|
+
const connector = createGmailConnector({ fetchFn });
|
|
308
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
309
|
+
// Use new watermarkMs format.
|
|
310
|
+
const cursor: ConnectorCursor = {
|
|
311
|
+
kind: GMAIL_CURSOR_KIND,
|
|
312
|
+
value: JSON.stringify({ watermarkMs: String(Number(T1) - 1000), skippedIds: {}, seenIds: {} }),
|
|
313
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
317
|
+
|
|
318
|
+
assert.equal(result.newDocs.length, 2);
|
|
319
|
+
assert.equal(result.newDocs[0].source.connector, GMAIL_CONNECTOR_ID);
|
|
320
|
+
assert.equal(result.newDocs[0].source.externalId, "msg-id-001");
|
|
321
|
+
assert.equal(result.newDocs[0].source.externalRevision, T1);
|
|
322
|
+
assert.equal(result.newDocs[0].title, "Subject One");
|
|
323
|
+
assert.ok(result.newDocs[0].content.includes("Hello world from message 001"));
|
|
324
|
+
assert.equal(result.newDocs[1].source.externalId, "msg-id-002");
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("watermark advances to the highest internalDate of successfully processed messages", async () => {
|
|
328
|
+
const msg1 = makeMessage("msg-id-a1", T1, "message a");
|
|
329
|
+
const msg2 = makeMessage("msg-id-a2", T3, "message b"); // highest
|
|
330
|
+
|
|
331
|
+
const fetchFn = makeFetch([
|
|
332
|
+
tokenHandler(),
|
|
333
|
+
listHandler([makeMessageRef("msg-id-a1"), makeMessageRef("msg-id-a2")]),
|
|
334
|
+
getHandler({ "msg-id-a1": msg1, "msg-id-a2": msg2 }),
|
|
335
|
+
]);
|
|
336
|
+
const connector = createGmailConnector({ fetchFn });
|
|
337
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
338
|
+
const cursor: ConnectorCursor = {
|
|
339
|
+
kind: GMAIL_CURSOR_KIND,
|
|
340
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
341
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const result = await connector.syncIncremental({ cursor, config });
|
|
345
|
+
|
|
346
|
+
// Watermark must be stored as epoch-ms string (Thread 1 precision fix).
|
|
347
|
+
const payload = JSON.parse(result.nextCursor.value) as { watermarkMs: string };
|
|
348
|
+
assert.ok(typeof payload.watermarkMs === "string", "cursor must use watermarkMs");
|
|
349
|
+
// Watermark should equal T3 (the highest internalDate).
|
|
350
|
+
assert.equal(Number(payload.watermarkMs), Number(T3));
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
// Watermark does NOT advance on partial drain (Codex P1)
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
test("watermark does NOT advance when nextPageToken present (partial drain)", async () => {
|
|
358
|
+
// Simulate a paginated list: first page has a nextPageToken, second call
|
|
359
|
+
// returns empty — but the cap is NOT hit here; we're testing that having
|
|
360
|
+
// a nextPageToken path that is followed and then exhausted DOES advance.
|
|
361
|
+
// The opposite: if a nextPageToken causes the second list call to fail
|
|
362
|
+
// with a transient error, we never mark the list fully drained.
|
|
363
|
+
const initialWatermark = new Date(Number(T1)).toISOString();
|
|
364
|
+
const msg1 = makeMessage("msg-partial-1", T2, "partial drain message");
|
|
365
|
+
|
|
366
|
+
let listCallCount = 0;
|
|
367
|
+
const partialDrainFetch = makeFetch([
|
|
368
|
+
tokenHandler(),
|
|
369
|
+
{
|
|
370
|
+
// First list call returns a nextPageToken (partial).
|
|
371
|
+
// Second call throws a transient error mid-drain.
|
|
372
|
+
match: (url) => url.startsWith("https://gmail.googleapis.com/") && url.includes("/messages") && !url.includes("format=full"),
|
|
373
|
+
respond: () => {
|
|
374
|
+
listCallCount++;
|
|
375
|
+
if (listCallCount === 1) {
|
|
376
|
+
return {
|
|
377
|
+
status: 200,
|
|
378
|
+
data: { messages: [{ id: "msg-partial-1" }], nextPageToken: "pg2" },
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
// Second page: transient error — list not fully drained.
|
|
382
|
+
return {
|
|
383
|
+
status: 503,
|
|
384
|
+
data: { error: { message: "service unavailable", code: 503 } },
|
|
385
|
+
};
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
getHandler({ "msg-partial-1": msg1 }),
|
|
389
|
+
]);
|
|
390
|
+
|
|
391
|
+
const connector = createGmailConnector({ fetchFn: partialDrainFetch });
|
|
392
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
393
|
+
const cursor: ConnectorCursor = {
|
|
394
|
+
kind: GMAIL_CURSOR_KIND,
|
|
395
|
+
value: JSON.stringify({ watermarkMs: String(new Date(initialWatermark).getTime()), skippedIds: {}, seenIds: {} }),
|
|
396
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// The 503 on the second list page rethrows (transient), stopping the pass.
|
|
400
|
+
await assert.rejects(
|
|
401
|
+
connector.syncIncremental({ cursor, config }),
|
|
402
|
+
/service unavailable/,
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("watermark advances when list is fully drained (no nextPageToken)", async () => {
|
|
407
|
+
const msg1 = makeMessage("msg-full-1", T1, "message 1");
|
|
408
|
+
const msg2 = makeMessage("msg-full-2", T3, "message 2");
|
|
409
|
+
|
|
410
|
+
const fetchFn = makeFetch([
|
|
411
|
+
tokenHandler(),
|
|
412
|
+
// No nextPageToken — fully drained.
|
|
413
|
+
listHandler([makeMessageRef("msg-full-1"), makeMessageRef("msg-full-2")]),
|
|
414
|
+
getHandler({ "msg-full-1": msg1, "msg-full-2": msg2 }),
|
|
415
|
+
]);
|
|
416
|
+
const connector = createGmailConnector({ fetchFn });
|
|
417
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
418
|
+
const initialWatermark = new Date(Number(T1) - 1000).toISOString();
|
|
419
|
+
const cursor: ConnectorCursor = {
|
|
420
|
+
kind: GMAIL_CURSOR_KIND,
|
|
421
|
+
value: JSON.stringify({ watermarkMs: String(new Date(initialWatermark).getTime()), skippedIds: {}, seenIds: {} }),
|
|
422
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const result = await connector.syncIncremental({ cursor, config });
|
|
426
|
+
|
|
427
|
+
const payload = JSON.parse(result.nextCursor.value) as { watermarkMs: string };
|
|
428
|
+
// Watermark should advance to T3 (the highest), stored as epoch-ms string.
|
|
429
|
+
assert.equal(Number(payload.watermarkMs), Number(T3));
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
// Watermark does NOT advance when all messages fail or are skipped
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
|
|
436
|
+
test("watermark does NOT advance when all messages are inaccessible (404)", async () => {
|
|
437
|
+
const initialWatermark = new Date(Number(T1)).toISOString();
|
|
438
|
+
const fetchFn = makeFetch([
|
|
439
|
+
tokenHandler(),
|
|
440
|
+
listHandler([makeMessageRef("msg-404"), makeMessageRef("msg-also-404")]),
|
|
441
|
+
getHandler({}, { "msg-404": 404, "msg-also-404": 404 }),
|
|
442
|
+
]);
|
|
443
|
+
const connector = createGmailConnector({ fetchFn });
|
|
444
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
445
|
+
const cursor: ConnectorCursor = {
|
|
446
|
+
kind: GMAIL_CURSOR_KIND,
|
|
447
|
+
value: JSON.stringify({ watermarkMs: String(new Date(initialWatermark).getTime()), skippedIds: {}, seenIds: {} }),
|
|
448
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
452
|
+
|
|
453
|
+
// No docs, watermark unchanged.
|
|
454
|
+
assert.equal(result.newDocs.length, 0);
|
|
455
|
+
assert.equal(result.skippedInaccessible, 2);
|
|
456
|
+
const payload = JSON.parse(result.nextCursor.value) as { watermarkMs: string };
|
|
457
|
+
// Watermark must remain at T1 (epoch ms).
|
|
458
|
+
assert.equal(Number(payload.watermarkMs), Number(T1));
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// ---------------------------------------------------------------------------
|
|
462
|
+
// Skip inaccessible messages (404 / 403) — terminal, continue the pass
|
|
463
|
+
// ---------------------------------------------------------------------------
|
|
464
|
+
|
|
465
|
+
test("a 404 on a single message skips it without stopping the pass", async () => {
|
|
466
|
+
const msgGood = makeMessage("msg-good-1", T2, "good message body");
|
|
467
|
+
|
|
468
|
+
const fetchFn = makeFetch([
|
|
469
|
+
tokenHandler(),
|
|
470
|
+
listHandler([makeMessageRef("msg-404"), makeMessageRef("msg-good-1")]),
|
|
471
|
+
getHandler({ "msg-good-1": msgGood }, { "msg-404": 404 }),
|
|
472
|
+
]);
|
|
473
|
+
const connector = createGmailConnector({ fetchFn });
|
|
474
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
475
|
+
const cursor: ConnectorCursor = {
|
|
476
|
+
kind: GMAIL_CURSOR_KIND,
|
|
477
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
478
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
482
|
+
|
|
483
|
+
assert.equal(result.newDocs.length, 1);
|
|
484
|
+
assert.equal(result.newDocs[0].source.externalId, "msg-good-1");
|
|
485
|
+
assert.equal(result.skippedInaccessible, 1);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("a 403 permission-denied is terminal (skip-and-continue)", async () => {
|
|
489
|
+
const msgGood = makeMessage("msg-good-2", T2, "good message body 2");
|
|
490
|
+
|
|
491
|
+
const fetchFn = makeFetch([
|
|
492
|
+
tokenHandler(),
|
|
493
|
+
listHandler([makeMessageRef("msg-403"), makeMessageRef("msg-good-2")]),
|
|
494
|
+
getHandler({ "msg-good-2": msgGood }, { "msg-403": 403 }),
|
|
495
|
+
]);
|
|
496
|
+
const connector = createGmailConnector({ fetchFn });
|
|
497
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
498
|
+
const cursor: ConnectorCursor = {
|
|
499
|
+
kind: GMAIL_CURSOR_KIND,
|
|
500
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
501
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
505
|
+
|
|
506
|
+
assert.equal(result.newDocs.length, 1);
|
|
507
|
+
assert.equal(result.newDocs[0].source.externalId, "msg-good-2");
|
|
508
|
+
assert.equal(result.skippedInaccessible, 1);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
// Transient error rethrow (429 / 5xx / AbortError / network)
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
|
|
515
|
+
test("a transient 429 re-throws and the cursor does NOT advance", async () => {
|
|
516
|
+
let callCount = 0;
|
|
517
|
+
const fetchFn = makeFetch([
|
|
518
|
+
tokenHandler(),
|
|
519
|
+
listHandler([makeMessageRef("msg-429")]),
|
|
520
|
+
{
|
|
521
|
+
match: (url) => url.includes("format=full"),
|
|
522
|
+
respond: () => {
|
|
523
|
+
callCount++;
|
|
524
|
+
return {
|
|
525
|
+
status: 429,
|
|
526
|
+
data: { error: { message: "rate limit exceeded", code: 429 } },
|
|
527
|
+
};
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
]);
|
|
531
|
+
const connector = createGmailConnector({ fetchFn });
|
|
532
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
533
|
+
const cursor: ConnectorCursor = {
|
|
534
|
+
kind: GMAIL_CURSOR_KIND,
|
|
535
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
536
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
await assert.rejects(
|
|
540
|
+
connector.syncIncremental({ cursor, config }),
|
|
541
|
+
/rate limit/,
|
|
542
|
+
);
|
|
543
|
+
assert.equal(callCount, 1);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test("a transient 503 re-throws", async () => {
|
|
547
|
+
const fetchFn = makeFetch([
|
|
548
|
+
tokenHandler(),
|
|
549
|
+
listHandler([makeMessageRef("msg-503")]),
|
|
550
|
+
{
|
|
551
|
+
match: (url) => url.includes("format=full"),
|
|
552
|
+
respond: () => ({
|
|
553
|
+
status: 503,
|
|
554
|
+
data: { error: { message: "service unavailable", code: 503 } },
|
|
555
|
+
}),
|
|
556
|
+
},
|
|
557
|
+
]);
|
|
558
|
+
const connector = createGmailConnector({ fetchFn });
|
|
559
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
560
|
+
const cursor: ConnectorCursor = {
|
|
561
|
+
kind: GMAIL_CURSOR_KIND,
|
|
562
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
563
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
await assert.rejects(
|
|
567
|
+
connector.syncIncremental({ cursor, config }),
|
|
568
|
+
/service unavailable/,
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("an AbortError raised mid-fetch re-throws", async () => {
|
|
573
|
+
const fetchFn = makeFetch([
|
|
574
|
+
tokenHandler(),
|
|
575
|
+
listHandler([makeMessageRef("msg-abort")]),
|
|
576
|
+
{
|
|
577
|
+
match: (url) => url.includes("format=full"),
|
|
578
|
+
respond: () => {
|
|
579
|
+
throw Object.assign(new Error("request aborted"), { name: "AbortError" });
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
]);
|
|
583
|
+
const connector = createGmailConnector({ fetchFn });
|
|
584
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
585
|
+
const cursor: ConnectorCursor = {
|
|
586
|
+
kind: GMAIL_CURSOR_KIND,
|
|
587
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
588
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
await assert.rejects(
|
|
592
|
+
connector.syncIncremental({ cursor, config }),
|
|
593
|
+
/aborted/,
|
|
594
|
+
);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("a network-layer ECONNRESET re-throws as transient", async () => {
|
|
598
|
+
const fetchFn = makeFetch([
|
|
599
|
+
tokenHandler(),
|
|
600
|
+
listHandler([makeMessageRef("msg-econnreset")]),
|
|
601
|
+
{
|
|
602
|
+
match: (url) => url.includes("format=full"),
|
|
603
|
+
respond: () => {
|
|
604
|
+
throw Object.assign(new Error("socket hang up"), { code: "ECONNRESET" });
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
]);
|
|
608
|
+
const connector = createGmailConnector({ fetchFn });
|
|
609
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
610
|
+
const cursor: ConnectorCursor = {
|
|
611
|
+
kind: GMAIL_CURSOR_KIND,
|
|
612
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
613
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
await assert.rejects(
|
|
617
|
+
connector.syncIncremental({ cursor, config }),
|
|
618
|
+
/socket hang up/,
|
|
619
|
+
);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
// AbortSignal honored between messages
|
|
624
|
+
// ---------------------------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
test("syncIncremental honors abortSignal between messages", async () => {
|
|
627
|
+
const controller = new AbortController();
|
|
628
|
+
let messageGetCount = 0;
|
|
629
|
+
|
|
630
|
+
const msg1 = makeMessage("msg-sig-1", T1, "first message");
|
|
631
|
+
const msg2 = makeMessage("msg-sig-2", T2, "second message");
|
|
632
|
+
|
|
633
|
+
const fetchFn = makeFetch([
|
|
634
|
+
tokenHandler(),
|
|
635
|
+
listHandler([makeMessageRef("msg-sig-1"), makeMessageRef("msg-sig-2")]),
|
|
636
|
+
{
|
|
637
|
+
match: (url) => url.includes("format=full"),
|
|
638
|
+
respond: (url) => {
|
|
639
|
+
messageGetCount++;
|
|
640
|
+
if (messageGetCount === 1) {
|
|
641
|
+
// Abort after the first message is fetched.
|
|
642
|
+
controller.abort();
|
|
643
|
+
}
|
|
644
|
+
const id = (url.match(/\/messages\/([^?]+)/) ?? [])[1] ?? "";
|
|
645
|
+
const msg = id === "msg-sig-1" ? msg1 : msg2;
|
|
646
|
+
return { status: 200, data: msg };
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
]);
|
|
650
|
+
const connector = createGmailConnector({ fetchFn });
|
|
651
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
652
|
+
const cursor: ConnectorCursor = {
|
|
653
|
+
kind: GMAIL_CURSOR_KIND,
|
|
654
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
655
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
await assert.rejects(
|
|
659
|
+
connector.syncIncremental({ cursor, config, abortSignal: controller.signal }),
|
|
660
|
+
/aborted/,
|
|
661
|
+
);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// ---------------------------------------------------------------------------
|
|
665
|
+
// Empty and too-large message handling
|
|
666
|
+
// ---------------------------------------------------------------------------
|
|
667
|
+
|
|
668
|
+
test("messages with empty body are skipped (skippedEmpty)", async () => {
|
|
669
|
+
const emptyMsg: GmailMessage = {
|
|
670
|
+
id: "msg-empty",
|
|
671
|
+
internalDate: T1,
|
|
672
|
+
payload: { mimeType: "text/plain", body: { data: "" } },
|
|
673
|
+
};
|
|
674
|
+
const goodMsg = makeMessage("msg-good-3", T2, "good content here");
|
|
675
|
+
|
|
676
|
+
const fetchFn = makeFetch([
|
|
677
|
+
tokenHandler(),
|
|
678
|
+
listHandler([makeMessageRef("msg-empty"), makeMessageRef("msg-good-3")]),
|
|
679
|
+
getHandler({ "msg-empty": emptyMsg, "msg-good-3": goodMsg }),
|
|
680
|
+
]);
|
|
681
|
+
const connector = createGmailConnector({ fetchFn });
|
|
682
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
683
|
+
const cursor: ConnectorCursor = {
|
|
684
|
+
kind: GMAIL_CURSOR_KIND,
|
|
685
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
686
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
690
|
+
|
|
691
|
+
assert.equal(result.skippedEmpty, 1);
|
|
692
|
+
assert.equal(result.newDocs.length, 1);
|
|
693
|
+
assert.equal(result.newDocs[0].source.externalId, "msg-good-3");
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
test("watermark advances past empty messages on full drain (immutable skip)", async () => {
|
|
697
|
+
// Gmail messages are immutable. An empty message must not stall the watermark
|
|
698
|
+
// forever — the Cursor Medium review fix records its internalDate and advances.
|
|
699
|
+
const emptyMsg: GmailMessage = {
|
|
700
|
+
id: "msg-empty-adv",
|
|
701
|
+
internalDate: T3, // highest internalDate in the batch
|
|
702
|
+
payload: { mimeType: "text/plain", body: { data: "" } },
|
|
703
|
+
};
|
|
704
|
+
const goodMsg = makeMessage("msg-good-adv", T2, "good content");
|
|
705
|
+
|
|
706
|
+
const fetchFn = makeFetch([
|
|
707
|
+
tokenHandler(),
|
|
708
|
+
// No nextPageToken — fully drained.
|
|
709
|
+
listHandler([makeMessageRef("msg-good-adv"), makeMessageRef("msg-empty-adv")]),
|
|
710
|
+
getHandler({ "msg-good-adv": goodMsg, "msg-empty-adv": emptyMsg }),
|
|
711
|
+
]);
|
|
712
|
+
const connector = createGmailConnector({ fetchFn });
|
|
713
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
714
|
+
const initialWatermark = new Date(Number(T1)).toISOString();
|
|
715
|
+
const cursor: ConnectorCursor = {
|
|
716
|
+
kind: GMAIL_CURSOR_KIND,
|
|
717
|
+
value: JSON.stringify({ watermarkMs: String(new Date(initialWatermark).getTime()), skippedIds: {}, seenIds: {} }),
|
|
718
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
722
|
+
|
|
723
|
+
assert.equal(result.skippedEmpty, 1);
|
|
724
|
+
assert.equal(result.newDocs.length, 1);
|
|
725
|
+
// Watermark should advance to T3 (the empty message's internalDate) so the
|
|
726
|
+
// next poll does not re-fetch it.
|
|
727
|
+
const payload = JSON.parse(result.nextCursor.value) as { watermarkMs: string };
|
|
728
|
+
assert.equal(Number(payload.watermarkMs), Number(T3), "watermark must advance past immutable empty message");
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// ---------------------------------------------------------------------------
|
|
732
|
+
// isTransientGmailError classification
|
|
733
|
+
// ---------------------------------------------------------------------------
|
|
734
|
+
|
|
735
|
+
test("isTransientGmailError classifies common error shapes", () => {
|
|
736
|
+
// Terminal — skip-and-continue.
|
|
737
|
+
assert.equal(isTransientGmailError({ status: 404 }), false);
|
|
738
|
+
assert.equal(isTransientGmailError({ response: { status: 403 } }), false);
|
|
739
|
+
assert.equal(isTransientGmailError({ response: { status: 400 } }), false);
|
|
740
|
+
assert.equal(isTransientGmailError({ response: { status: 410 } }), false);
|
|
741
|
+
// Transient — re-throw.
|
|
742
|
+
assert.equal(isTransientGmailError({ response: { status: 429 } }), true);
|
|
743
|
+
assert.equal(isTransientGmailError({ response: { status: 500 } }), true);
|
|
744
|
+
assert.equal(isTransientGmailError({ response: { status: 503 } }), true);
|
|
745
|
+
assert.equal(isTransientGmailError({ gmailStatus: 429 }), true);
|
|
746
|
+
assert.equal(isTransientGmailError({ gmailStatus: 503 }), true);
|
|
747
|
+
assert.equal(isTransientGmailError({ status: 504 }), true);
|
|
748
|
+
// String-numeric codes.
|
|
749
|
+
assert.equal(isTransientGmailError({ code: "429" }), true);
|
|
750
|
+
assert.equal(isTransientGmailError({ code: "503" }), true);
|
|
751
|
+
// Network errors.
|
|
752
|
+
assert.equal(isTransientGmailError({ code: "ECONNRESET" }), true);
|
|
753
|
+
assert.equal(isTransientGmailError({ code: "ETIMEDOUT" }), true);
|
|
754
|
+
assert.equal(isTransientGmailError({ code: "ENOTFOUND" }), true);
|
|
755
|
+
assert.equal(isTransientGmailError({ code: "EAI_AGAIN" }), true);
|
|
756
|
+
// AbortError.
|
|
757
|
+
assert.equal(isTransientGmailError({ name: "AbortError" }), true);
|
|
758
|
+
// Bare Error with no metadata — conservatively transient.
|
|
759
|
+
assert.equal(isTransientGmailError(new Error("unknown")), true);
|
|
760
|
+
// Non-objects.
|
|
761
|
+
assert.equal(isTransientGmailError(null), false);
|
|
762
|
+
assert.equal(isTransientGmailError(undefined), false);
|
|
763
|
+
assert.equal(isTransientGmailError("oops"), false);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
// ---------------------------------------------------------------------------
|
|
767
|
+
// Cursor shape and validation
|
|
768
|
+
// ---------------------------------------------------------------------------
|
|
769
|
+
|
|
770
|
+
test("syncIncremental rejects a cursor of an unexpected kind", async () => {
|
|
771
|
+
const connector = createGmailConnector({
|
|
772
|
+
fetchFn: makeFetch([tokenHandler()]),
|
|
773
|
+
});
|
|
774
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
775
|
+
const cursor: ConnectorCursor = {
|
|
776
|
+
kind: "wrong-kind",
|
|
777
|
+
value: "x",
|
|
778
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
await assert.rejects(
|
|
782
|
+
connector.syncIncremental({ cursor, config }),
|
|
783
|
+
/unexpected cursor kind/,
|
|
784
|
+
);
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
test("validateConfig is enforced again on every sync pass", async () => {
|
|
788
|
+
const connector = createGmailConnector({
|
|
789
|
+
fetchFn: makeFetch([tokenHandler()]),
|
|
790
|
+
});
|
|
791
|
+
const badConfig = { clientId: "ok", clientSecret: "ok", refreshToken: "" } as unknown as import("./framework.js").ConnectorConfig;
|
|
792
|
+
const cursor: ConnectorCursor = {
|
|
793
|
+
kind: GMAIL_CURSOR_KIND,
|
|
794
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
795
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
await assert.rejects(
|
|
799
|
+
connector.syncIncremental({ cursor, config: badConfig }),
|
|
800
|
+
/refreshToken/,
|
|
801
|
+
);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// ---------------------------------------------------------------------------
|
|
805
|
+
// Helper function tests
|
|
806
|
+
// ---------------------------------------------------------------------------
|
|
807
|
+
|
|
808
|
+
test("internalDateToEpochSeconds converts epoch-ms string correctly", () => {
|
|
809
|
+
assert.equal(internalDateToEpochSeconds("1745000000000"), 1745000000);
|
|
810
|
+
assert.equal(internalDateToEpochSeconds("0"), 0);
|
|
811
|
+
assert.equal(internalDateToEpochSeconds(""), 0);
|
|
812
|
+
assert.equal(internalDateToEpochSeconds("not-a-number"), 0);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
test("internalDateToIso converts epoch-ms string to ISO 8601", () => {
|
|
816
|
+
const iso = internalDateToIso("1745000000000");
|
|
817
|
+
assert.ok(iso.startsWith("2025"), `expected 2025 date, got ${iso}`);
|
|
818
|
+
assert.equal(internalDateToIso(""), "");
|
|
819
|
+
assert.equal(internalDateToIso("not-a-number"), "");
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
test("buildListQuery combines watermark and user query correctly", () => {
|
|
823
|
+
assert.equal(buildListQuery(1745000000, "in:inbox"), "after:1745000000 in:inbox");
|
|
824
|
+
assert.equal(buildListQuery(0, "in:inbox"), "in:inbox");
|
|
825
|
+
assert.equal(buildListQuery(1745000000, ""), "after:1745000000");
|
|
826
|
+
assert.equal(buildListQuery(0, ""), "");
|
|
827
|
+
assert.equal(buildListQuery(1745000000, " label:work "), "after:1745000000 label:work");
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// ---------------------------------------------------------------------------
|
|
831
|
+
// Multipart / HTML body extraction
|
|
832
|
+
// ---------------------------------------------------------------------------
|
|
833
|
+
|
|
834
|
+
test("incremental sync extracts text/plain from multipart/alternative messages", async () => {
|
|
835
|
+
const multipartMsg: GmailMessage = {
|
|
836
|
+
id: "msg-multipart",
|
|
837
|
+
internalDate: T1,
|
|
838
|
+
payload: {
|
|
839
|
+
mimeType: "multipart/alternative",
|
|
840
|
+
parts: [
|
|
841
|
+
{
|
|
842
|
+
mimeType: "text/plain",
|
|
843
|
+
body: {
|
|
844
|
+
data: Buffer.from("Plain text body here", "utf-8").toString("base64url"),
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
mimeType: "text/html",
|
|
849
|
+
body: {
|
|
850
|
+
data: Buffer.from("<html><body>HTML body here</body></html>", "utf-8").toString("base64url"),
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
},
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
const fetchFn = makeFetch([
|
|
858
|
+
tokenHandler(),
|
|
859
|
+
listHandler([makeMessageRef("msg-multipart")]),
|
|
860
|
+
getHandler({ "msg-multipart": multipartMsg }),
|
|
861
|
+
]);
|
|
862
|
+
const connector = createGmailConnector({ fetchFn });
|
|
863
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
864
|
+
const cursor: ConnectorCursor = {
|
|
865
|
+
kind: GMAIL_CURSOR_KIND,
|
|
866
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
867
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const result = await connector.syncIncremental({ cursor, config });
|
|
871
|
+
|
|
872
|
+
assert.equal(result.newDocs.length, 1);
|
|
873
|
+
// Should prefer text/plain over text/html.
|
|
874
|
+
assert.ok(result.newDocs[0].content.includes("Plain text body here"), "should use plain text part");
|
|
875
|
+
assert.ok(!result.newDocs[0].content.includes("HTML body"), "should not include HTML part");
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
test("incremental sync extracts plain text from HTML when no text/plain part exists", async () => {
|
|
879
|
+
const htmlOnlyMsg: GmailMessage = {
|
|
880
|
+
id: "msg-html-only",
|
|
881
|
+
internalDate: T1,
|
|
882
|
+
payload: {
|
|
883
|
+
mimeType: "text/html",
|
|
884
|
+
body: {
|
|
885
|
+
data: Buffer.from("<p>Hello <strong>World</strong></p>", "utf-8").toString("base64url"),
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
const fetchFn = makeFetch([
|
|
891
|
+
tokenHandler(),
|
|
892
|
+
listHandler([makeMessageRef("msg-html-only")]),
|
|
893
|
+
getHandler({ "msg-html-only": htmlOnlyMsg }),
|
|
894
|
+
]);
|
|
895
|
+
const connector = createGmailConnector({ fetchFn });
|
|
896
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
897
|
+
const cursor: ConnectorCursor = {
|
|
898
|
+
kind: GMAIL_CURSOR_KIND,
|
|
899
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
900
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
const result = await connector.syncIncremental({ cursor, config });
|
|
904
|
+
|
|
905
|
+
assert.equal(result.newDocs.length, 1);
|
|
906
|
+
assert.ok(result.newDocs[0].content.includes("Hello"), "should have stripped HTML content");
|
|
907
|
+
assert.ok(!result.newDocs[0].content.includes("<p>"), "should not contain raw HTML tags");
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// ---------------------------------------------------------------------------
|
|
911
|
+
// OAuth2 token exchange failure
|
|
912
|
+
// ---------------------------------------------------------------------------
|
|
913
|
+
|
|
914
|
+
test("OAuth2 token exchange failure propagates as transient error", async () => {
|
|
915
|
+
const failFetch = makeFetch([
|
|
916
|
+
{
|
|
917
|
+
match: (url) => url.startsWith("https://oauth2.googleapis.com/"),
|
|
918
|
+
respond: () => ({
|
|
919
|
+
status: 503,
|
|
920
|
+
data: { error: "service_unavailable", error_description: "try again" },
|
|
921
|
+
}),
|
|
922
|
+
},
|
|
923
|
+
]);
|
|
924
|
+
const connector = createGmailConnector({ fetchFn: failFetch });
|
|
925
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
926
|
+
const cursor: ConnectorCursor = {
|
|
927
|
+
kind: GMAIL_CURSOR_KIND,
|
|
928
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
929
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
await assert.rejects(
|
|
933
|
+
connector.syncIncremental({ cursor, config }),
|
|
934
|
+
/OAuth2 token exchange failed/,
|
|
935
|
+
);
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
test("OAuth2 token exchange 401 failure (invalid credentials) also throws", async () => {
|
|
939
|
+
const failFetch = makeFetch([
|
|
940
|
+
{
|
|
941
|
+
match: (url) => url.startsWith("https://oauth2.googleapis.com/"),
|
|
942
|
+
respond: () => ({
|
|
943
|
+
status: 401,
|
|
944
|
+
data: { error: "invalid_client", error_description: "bad credentials" },
|
|
945
|
+
}),
|
|
946
|
+
},
|
|
947
|
+
]);
|
|
948
|
+
const connector = createGmailConnector({ fetchFn: failFetch });
|
|
949
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
950
|
+
const cursor: ConnectorCursor = {
|
|
951
|
+
kind: GMAIL_CURSOR_KIND,
|
|
952
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
953
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
await assert.rejects(
|
|
957
|
+
connector.syncIncremental({ cursor, config }),
|
|
958
|
+
/OAuth2 token exchange failed/,
|
|
959
|
+
);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// ---------------------------------------------------------------------------
|
|
963
|
+
// Thread 1 regression: watermark precision — sub-second messages
|
|
964
|
+
// ---------------------------------------------------------------------------
|
|
965
|
+
|
|
966
|
+
test("cursor stores watermarkMs as epoch-ms string, not ISO (Thread 1 precision)", async () => {
|
|
967
|
+
// Watermark with sub-second precision (ms digit is non-zero).
|
|
968
|
+
const preciseMs = "1745000000500"; // epoch ms ending in 500 ms
|
|
969
|
+
const msg = makeMessage("msg-precise-1", preciseMs, "precise message");
|
|
970
|
+
|
|
971
|
+
const fetchFn = makeFetch([
|
|
972
|
+
tokenHandler(),
|
|
973
|
+
listHandler([makeMessageRef("msg-precise-1")]),
|
|
974
|
+
getHandler({ "msg-precise-1": msg }),
|
|
975
|
+
]);
|
|
976
|
+
const connector = createGmailConnector({ fetchFn });
|
|
977
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
978
|
+
const cursor: ConnectorCursor = {
|
|
979
|
+
kind: GMAIL_CURSOR_KIND,
|
|
980
|
+
value: JSON.stringify({ watermarkMs: String(Number(preciseMs) - 1000), skippedIds: {}, seenIds: {} }),
|
|
981
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
const result = await connector.syncIncremental({ cursor, config });
|
|
985
|
+
|
|
986
|
+
const payload = JSON.parse(result.nextCursor.value) as Record<string, unknown>;
|
|
987
|
+
// Must store exact ms, not an ISO string.
|
|
988
|
+
assert.ok(typeof payload.watermarkMs === "string", "cursor must use watermarkMs");
|
|
989
|
+
assert.equal(payload.watermarkIso, undefined, "cursor must NOT use watermarkIso");
|
|
990
|
+
// Must preserve sub-second precision: the stored value must equal the exact
|
|
991
|
+
// internalDate ms, not a truncation to the second boundary.
|
|
992
|
+
assert.equal(
|
|
993
|
+
Number(payload.watermarkMs as string),
|
|
994
|
+
Number(preciseMs),
|
|
995
|
+
"watermark must preserve sub-second precision (not truncated to second)",
|
|
996
|
+
);
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
test("sub-second messages in seenIds are not re-imported on the next poll (Thread 1)", async () => {
|
|
1000
|
+
// Two messages with internalDate within the same second: 1745000000200 and
|
|
1001
|
+
// 1745000000800. After poll 1 both are processed and watermark = 1745000000800.
|
|
1002
|
+
// Poll 2 queries after:1745000000 (same second floor). Both message ids must
|
|
1003
|
+
// be skipped via seenIds — not re-imported.
|
|
1004
|
+
const msA = "1745000000200";
|
|
1005
|
+
const msB = "1745000000800"; // highest — becomes watermark after poll 1
|
|
1006
|
+
const msgA = makeMessage("msg-subsec-a", msA, "sub-second message A");
|
|
1007
|
+
const msgB = makeMessage("msg-subsec-b", msB, "sub-second message B");
|
|
1008
|
+
|
|
1009
|
+
// --- Poll 1: process both messages ---
|
|
1010
|
+
const fetchFn1 = makeFetch([
|
|
1011
|
+
tokenHandler(),
|
|
1012
|
+
listHandler([makeMessageRef("msg-subsec-a"), makeMessageRef("msg-subsec-b")]),
|
|
1013
|
+
getHandler({ "msg-subsec-a": msgA, "msg-subsec-b": msgB }),
|
|
1014
|
+
]);
|
|
1015
|
+
const connector1 = createGmailConnector({ fetchFn: fetchFn1 });
|
|
1016
|
+
const config = connector1.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1017
|
+
// Watermark starts within the SAME second as msA and msB (floor=1745000000).
|
|
1018
|
+
// Use 50ms before msA — this puts the initial watermark in second 1745000000
|
|
1019
|
+
// so the watermark advance from initial to msB stays within the same second
|
|
1020
|
+
// and seenIds are NOT cleared (they're still needed for sub-second dedup).
|
|
1021
|
+
const initialWatermarkMs = String(Number(msA) - 50); // 1745000000150, floor=1745000000
|
|
1022
|
+
const startCursor: ConnectorCursor = {
|
|
1023
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1024
|
+
value: JSON.stringify({ watermarkMs: initialWatermarkMs, skippedIds: {}, seenIds: {} }),
|
|
1025
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const result1 = await connector1.syncIncremental({ cursor: startCursor, config });
|
|
1029
|
+
assert.equal(result1.newDocs.length, 2, "poll 1 should import both messages");
|
|
1030
|
+
|
|
1031
|
+
// The cursor after poll 1 must carry seenIds for both messages (same-second dedup).
|
|
1032
|
+
const cursor1Payload = JSON.parse(result1.nextCursor.value) as {
|
|
1033
|
+
watermarkMs: string;
|
|
1034
|
+
seenIds: Record<string, string>;
|
|
1035
|
+
};
|
|
1036
|
+
assert.equal(Number(cursor1Payload.watermarkMs), Number(msB), "watermark after poll 1 must be msB");
|
|
1037
|
+
// All three timestamps (initial, msA, msB) are in the same second (floor=1745000000),
|
|
1038
|
+
// so seenIds must NOT be cleared — they're retained for same-second dedup.
|
|
1039
|
+
assert.equal(
|
|
1040
|
+
Math.floor(Number(initialWatermarkMs) / 1000),
|
|
1041
|
+
Math.floor(Number(msB) / 1000),
|
|
1042
|
+
"test invariant: initial watermark and msB must be in the same second",
|
|
1043
|
+
);
|
|
1044
|
+
assert.ok(
|
|
1045
|
+
cursor1Payload.seenIds["msg-subsec-a"] !== undefined,
|
|
1046
|
+
"seenIds must include msg-subsec-a for sub-second dedup",
|
|
1047
|
+
);
|
|
1048
|
+
assert.ok(
|
|
1049
|
+
cursor1Payload.seenIds["msg-subsec-b"] !== undefined,
|
|
1050
|
+
"seenIds must include msg-subsec-b for sub-second dedup",
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
// --- Poll 2: same messages returned by Gmail (after: is second-granular) ---
|
|
1054
|
+
let getCallCount = 0;
|
|
1055
|
+
const fetchFn2 = makeFetch([
|
|
1056
|
+
tokenHandler(),
|
|
1057
|
+
// Gmail re-returns the same two messages because after:floor(msB/1000) includes them.
|
|
1058
|
+
listHandler([makeMessageRef("msg-subsec-a"), makeMessageRef("msg-subsec-b")]),
|
|
1059
|
+
{
|
|
1060
|
+
match: (url) => url.includes("format=full"),
|
|
1061
|
+
respond: () => {
|
|
1062
|
+
getCallCount++;
|
|
1063
|
+
return { status: 200, data: msgA };
|
|
1064
|
+
},
|
|
1065
|
+
},
|
|
1066
|
+
]);
|
|
1067
|
+
const connector2 = createGmailConnector({ fetchFn: fetchFn2 });
|
|
1068
|
+
const result2 = await connector2.syncIncremental({ cursor: result1.nextCursor, config });
|
|
1069
|
+
|
|
1070
|
+
// Both messages must be skipped via seenIds — no new docs, no API get calls.
|
|
1071
|
+
assert.equal(result2.newDocs.length, 0, "poll 2 must not re-import same-second messages");
|
|
1072
|
+
assert.equal(getCallCount, 0, "poll 2 must not call messages.get for seenIds messages");
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
test("backward-compat: old watermarkIso cursor is migrated to watermarkMs (Thread 1)", async () => {
|
|
1076
|
+
// An old cursor stored watermarkIso. The parser must convert it to watermarkMs
|
|
1077
|
+
// and the next cursor must use watermarkMs (never write watermarkIso back).
|
|
1078
|
+
const isoWatermark = new Date(Number(T1)).toISOString();
|
|
1079
|
+
const msg = makeMessage("msg-compat-1", T2, "compat migration test");
|
|
1080
|
+
|
|
1081
|
+
const fetchFn = makeFetch([
|
|
1082
|
+
tokenHandler(),
|
|
1083
|
+
listHandler([makeMessageRef("msg-compat-1")]),
|
|
1084
|
+
getHandler({ "msg-compat-1": msg }),
|
|
1085
|
+
]);
|
|
1086
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1087
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1088
|
+
// Old cursor format (no watermarkMs, no skippedIds, no seenIds).
|
|
1089
|
+
const legacyCursor: ConnectorCursor = {
|
|
1090
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1091
|
+
value: JSON.stringify({ watermarkIso: isoWatermark }),
|
|
1092
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
const result = await connector.syncIncremental({ cursor: legacyCursor, config });
|
|
1096
|
+
|
|
1097
|
+
assert.equal(result.newDocs.length, 1, "should still import messages from legacy cursor");
|
|
1098
|
+
const nextPayload = JSON.parse(result.nextCursor.value) as Record<string, unknown>;
|
|
1099
|
+
assert.ok(typeof nextPayload.watermarkMs === "string", "next cursor must use watermarkMs");
|
|
1100
|
+
assert.equal(nextPayload.watermarkIso, undefined, "next cursor must not use watermarkIso");
|
|
1101
|
+
assert.equal(Number(nextPayload.watermarkMs as string), Number(T2), "watermark must advance to T2");
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
// ---------------------------------------------------------------------------
|
|
1105
|
+
// Thread 2 regression: skipped messages recorded in skippedIds
|
|
1106
|
+
// ---------------------------------------------------------------------------
|
|
1107
|
+
|
|
1108
|
+
test("empty message id is recorded in skippedIds (Thread 2)", async () => {
|
|
1109
|
+
const emptyMsg: GmailMessage = {
|
|
1110
|
+
id: "msg-empty-skip",
|
|
1111
|
+
internalDate: T1,
|
|
1112
|
+
payload: { mimeType: "text/plain", body: { data: "" } },
|
|
1113
|
+
};
|
|
1114
|
+
|
|
1115
|
+
const fetchFn = makeFetch([
|
|
1116
|
+
tokenHandler(),
|
|
1117
|
+
listHandler([makeMessageRef("msg-empty-skip")]),
|
|
1118
|
+
getHandler({ "msg-empty-skip": emptyMsg }),
|
|
1119
|
+
]);
|
|
1120
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1121
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1122
|
+
const cursor: ConnectorCursor = {
|
|
1123
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1124
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
1125
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
1129
|
+
assert.equal(result.skippedEmpty, 1);
|
|
1130
|
+
|
|
1131
|
+
// The cursor must record the empty message id in skippedIds.
|
|
1132
|
+
const nextPayload = JSON.parse(result.nextCursor.value) as { skippedIds: Record<string, unknown> };
|
|
1133
|
+
assert.ok(
|
|
1134
|
+
typeof nextPayload.skippedIds["msg-empty-skip"] === "string" &&
|
|
1135
|
+
nextPayload.skippedIds["msg-empty-skip"].length > 0,
|
|
1136
|
+
"empty message id must be in skippedIds with an internalDate string",
|
|
1137
|
+
);
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
test("too-large message id is recorded in skippedIds (Thread 2)", async () => {
|
|
1141
|
+
// Build a message whose body exceeds MAX_TEXT_BYTES (2 MB).
|
|
1142
|
+
const largeBody = "x".repeat(2 * 1024 * 1024 + 1);
|
|
1143
|
+
const largeMsg: GmailMessage = {
|
|
1144
|
+
id: "msg-toolarge-skip",
|
|
1145
|
+
internalDate: T1,
|
|
1146
|
+
payload: {
|
|
1147
|
+
mimeType: "text/plain",
|
|
1148
|
+
body: { data: Buffer.from(largeBody, "utf-8").toString("base64url") },
|
|
1149
|
+
},
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
const fetchFn = makeFetch([
|
|
1153
|
+
tokenHandler(),
|
|
1154
|
+
listHandler([makeMessageRef("msg-toolarge-skip")]),
|
|
1155
|
+
getHandler({ "msg-toolarge-skip": largeMsg }),
|
|
1156
|
+
]);
|
|
1157
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1158
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1159
|
+
const cursor: ConnectorCursor = {
|
|
1160
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1161
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
1162
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
1166
|
+
assert.equal(result.skippedTooLarge, 1);
|
|
1167
|
+
|
|
1168
|
+
const nextPayload = JSON.parse(result.nextCursor.value) as { skippedIds: Record<string, unknown> };
|
|
1169
|
+
assert.ok(
|
|
1170
|
+
typeof nextPayload.skippedIds["msg-toolarge-skip"] === "string" &&
|
|
1171
|
+
nextPayload.skippedIds["msg-toolarge-skip"].length > 0,
|
|
1172
|
+
"too-large message id must be in skippedIds with an internalDate string",
|
|
1173
|
+
);
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
test("inaccessible (404) message id is recorded in skippedIds (Thread 2)", async () => {
|
|
1177
|
+
const fetchFn = makeFetch([
|
|
1178
|
+
tokenHandler(),
|
|
1179
|
+
listHandler([makeMessageRef("msg-404-skip")]),
|
|
1180
|
+
getHandler({}, { "msg-404-skip": 404 }),
|
|
1181
|
+
]);
|
|
1182
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1183
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1184
|
+
const cursor: ConnectorCursor = {
|
|
1185
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1186
|
+
value: JSON.stringify({ watermarkMs: "0", skippedIds: {}, seenIds: {} }),
|
|
1187
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
1191
|
+
assert.equal(result.skippedInaccessible, 1);
|
|
1192
|
+
|
|
1193
|
+
const nextPayload = JSON.parse(result.nextCursor.value) as { skippedIds: Record<string, unknown> };
|
|
1194
|
+
assert.ok(
|
|
1195
|
+
typeof nextPayload.skippedIds["msg-404-skip"] === "string" &&
|
|
1196
|
+
nextPayload.skippedIds["msg-404-skip"].length > 0,
|
|
1197
|
+
"inaccessible message id must be in skippedIds with a date string",
|
|
1198
|
+
);
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
test("skippedIds messages are not re-fetched on subsequent polls (Thread 2 stall fix)", async () => {
|
|
1202
|
+
// Poll 2 has the empty message id already in skippedIds from poll 1.
|
|
1203
|
+
// We verify messages.get is never called for it and it does not consume the cap.
|
|
1204
|
+
const goodMsg = makeMessage("msg-good-skip-bypass", T2, "good content for next poll");
|
|
1205
|
+
|
|
1206
|
+
let getCallsForSkipped = 0;
|
|
1207
|
+
const fetchFn = makeFetch([
|
|
1208
|
+
tokenHandler(),
|
|
1209
|
+
listHandler([
|
|
1210
|
+
makeMessageRef("msg-empty-already-skipped"),
|
|
1211
|
+
makeMessageRef("msg-good-skip-bypass"),
|
|
1212
|
+
]),
|
|
1213
|
+
{
|
|
1214
|
+
match: (url) => url.includes("format=full"),
|
|
1215
|
+
respond: (url) => {
|
|
1216
|
+
if (url.includes("msg-empty-already-skipped")) {
|
|
1217
|
+
getCallsForSkipped++;
|
|
1218
|
+
}
|
|
1219
|
+
return { status: 200, data: goodMsg };
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
1222
|
+
]);
|
|
1223
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1224
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1225
|
+
// Cursor carries the previously-skipped id in skippedIds.
|
|
1226
|
+
const cursor: ConnectorCursor = {
|
|
1227
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1228
|
+
value: JSON.stringify({
|
|
1229
|
+
watermarkMs: String(Number(T1) - 1000),
|
|
1230
|
+
skippedIds: { "msg-empty-already-skipped": true },
|
|
1231
|
+
seenIds: {},
|
|
1232
|
+
}),
|
|
1233
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
const result = (await connector.syncIncremental({ cursor, config })) as GmailSyncResult;
|
|
1237
|
+
|
|
1238
|
+
// Only the new good message is imported.
|
|
1239
|
+
assert.equal(result.newDocs.length, 1, "only the new good message should be imported");
|
|
1240
|
+
assert.equal(result.newDocs[0].source.externalId, "msg-good-skip-bypass");
|
|
1241
|
+
assert.equal(result.skippedEmpty, 0, "skipped-id messages must not count toward skippedEmpty again");
|
|
1242
|
+
assert.equal(getCallsForSkipped, 0, "messages.get must NOT be called for ids in skippedIds");
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
// ---------------------------------------------------------------------------
|
|
1246
|
+
// Thread 1 regression: after: query uses Math.floor (inclusive of watermark second)
|
|
1247
|
+
// (Codex P1 PRRT_kwDORJXyws59sh5H)
|
|
1248
|
+
// ---------------------------------------------------------------------------
|
|
1249
|
+
|
|
1250
|
+
test("after: query uses floor(watermarkMs/1000) — inclusive of the watermark second (Codex P1 PRRT_kwDORJXyws59sh5H)", async () => {
|
|
1251
|
+
// Watermark at exactly 1745000001000 ms (a second boundary): both floor and
|
|
1252
|
+
// ceil equal 1745000001. Use a sub-second watermark to distinguish them:
|
|
1253
|
+
// watermark = 1745000000500ms → floor = 1745000000, ceil = 1745000001.
|
|
1254
|
+
//
|
|
1255
|
+
// Floor is correct: Gmail's `after:N` matches internalDate > N*1000, so
|
|
1256
|
+
// floor ensures messages at the watermark second are still queryable.
|
|
1257
|
+
// Ceil would skip messages whose internalDate is exactly at the watermark
|
|
1258
|
+
// second boundary (between floor and ceil), causing permanent data loss.
|
|
1259
|
+
const subSecondWatermark = "1745000000500";
|
|
1260
|
+
|
|
1261
|
+
// Track what `after:` value is used in the list URL.
|
|
1262
|
+
let capturedAfterValue = "";
|
|
1263
|
+
const fetchFn = makeFetch([
|
|
1264
|
+
tokenHandler(),
|
|
1265
|
+
{
|
|
1266
|
+
match: (url) => url.includes("/messages") && !url.includes("format=full"),
|
|
1267
|
+
respond: (url) => {
|
|
1268
|
+
// Extract `after:N` from the encoded `q` parameter.
|
|
1269
|
+
const qMatch = url.match(/[?&]q=([^&]+)/);
|
|
1270
|
+
if (qMatch) {
|
|
1271
|
+
const q = decodeURIComponent(qMatch[1]);
|
|
1272
|
+
const afterMatch = q.match(/after:(\d+)/);
|
|
1273
|
+
if (afterMatch) capturedAfterValue = afterMatch[1];
|
|
1274
|
+
}
|
|
1275
|
+
return { status: 200, data: { messages: [] } };
|
|
1276
|
+
},
|
|
1277
|
+
},
|
|
1278
|
+
]);
|
|
1279
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1280
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1281
|
+
const cursor: ConnectorCursor = {
|
|
1282
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1283
|
+
value: JSON.stringify({ watermarkMs: subSecondWatermark, skippedIds: {}, seenIds: {} }),
|
|
1284
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
await connector.syncIncremental({ cursor, config });
|
|
1288
|
+
|
|
1289
|
+
// With Math.floor: floor(1745000000500 / 1000) = 1745000000 ✓
|
|
1290
|
+
// With Math.ceil: ceil(1745000000500 / 1000) = 1745000001 ✗ (misses boundary messages)
|
|
1291
|
+
assert.equal(
|
|
1292
|
+
capturedAfterValue,
|
|
1293
|
+
"1745000000",
|
|
1294
|
+
"after: must use Math.floor so messages at the watermark second boundary are not missed",
|
|
1295
|
+
);
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
// ---------------------------------------------------------------------------
|
|
1299
|
+
// Thread 2 regression: page-token resume prevents livelock
|
|
1300
|
+
// ---------------------------------------------------------------------------
|
|
1301
|
+
|
|
1302
|
+
test("cursor persists pageToken when cap is hit mid-page with more pages remaining (Thread 2 Codex P1)", async () => {
|
|
1303
|
+
// Set up: poll where cap is hit on page 1 and there is a page 2.
|
|
1304
|
+
// MAX_MESSAGES_PER_PASS = 200, but we test the mechanism with a cap hit
|
|
1305
|
+
// by placing enough messages to saturate the cap and having a nextPageToken.
|
|
1306
|
+
// We simulate this by using the cursor's pageToken field directly.
|
|
1307
|
+
//
|
|
1308
|
+
// Simplified test: verify cursor.pageToken is set when cap is hit mid-page.
|
|
1309
|
+
// We use a custom MAX_MESSAGES_PER_PASS-aware approach: add a nextPageToken
|
|
1310
|
+
// to a list response and verify the cursor stores it when cap hits.
|
|
1311
|
+
|
|
1312
|
+
// Use a listHandler that returns N messages (one below cap) + nextPageToken,
|
|
1313
|
+
// then a second page. The cap won't actually be hit with 1 message, so we
|
|
1314
|
+
// instead directly verify that the cursor's pageToken field is populated
|
|
1315
|
+
// when there are more pages and the processing stops mid-window.
|
|
1316
|
+
|
|
1317
|
+
// Simplified: Build a scenario where:
|
|
1318
|
+
// - Page 1 returns [msg-cap-1] with nextPageToken="page2-token"
|
|
1319
|
+
// - Page 2 would have more messages but we use a stub that verifies the
|
|
1320
|
+
// cursor stores pageToken="page2-token" if cap is hit.
|
|
1321
|
+
//
|
|
1322
|
+
// To trigger cap within a single-message page, we use the cursor's `pageToken`
|
|
1323
|
+
// resume path: start with a cursor that has pageToken="page2-token", verify
|
|
1324
|
+
// the list request uses it.
|
|
1325
|
+
|
|
1326
|
+
let listRequestUrls: string[] = [];
|
|
1327
|
+
const fetchFn = makeFetch([
|
|
1328
|
+
tokenHandler(),
|
|
1329
|
+
{
|
|
1330
|
+
match: (url) => url.includes("/messages") && !url.includes("format=full"),
|
|
1331
|
+
respond: (url) => {
|
|
1332
|
+
listRequestUrls.push(url);
|
|
1333
|
+
// Return empty — no messages, no nextPageToken.
|
|
1334
|
+
return { status: 200, data: {} };
|
|
1335
|
+
},
|
|
1336
|
+
},
|
|
1337
|
+
]);
|
|
1338
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1339
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1340
|
+
|
|
1341
|
+
// Cursor with a persisted pageToken from a previous cap-hit pass.
|
|
1342
|
+
const cursor: ConnectorCursor = {
|
|
1343
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1344
|
+
value: JSON.stringify({
|
|
1345
|
+
watermarkMs: String(Number(T1)),
|
|
1346
|
+
skippedIds: {},
|
|
1347
|
+
seenIds: {},
|
|
1348
|
+
pageToken: "synthetic-page2-token-from-prev-pass",
|
|
1349
|
+
}),
|
|
1350
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1353
|
+
await connector.syncIncremental({ cursor, config });
|
|
1354
|
+
|
|
1355
|
+
// The list request URL must include the persisted pageToken.
|
|
1356
|
+
assert.ok(listRequestUrls.length > 0, "at least one list request was made");
|
|
1357
|
+
assert.ok(
|
|
1358
|
+
listRequestUrls[0].includes("pageToken=synthetic-page2-token-from-prev-pass"),
|
|
1359
|
+
`list request must include the persisted pageToken; got: ${listRequestUrls[0]}`,
|
|
1360
|
+
);
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
test("cursor's pageToken is cleared when the after: window is fully drained (Thread 2)", async () => {
|
|
1364
|
+
// After a cap-hit pass stored a pageToken, a subsequent pass that fully
|
|
1365
|
+
// drains the window must clear the pageToken from the cursor.
|
|
1366
|
+
const msg1 = makeMessage("msg-drain-1", T2, "drain test message");
|
|
1367
|
+
|
|
1368
|
+
const fetchFn = makeFetch([
|
|
1369
|
+
tokenHandler(),
|
|
1370
|
+
// The list returns one message, no nextPageToken — fully drained.
|
|
1371
|
+
listHandler([makeMessageRef("msg-drain-1")]),
|
|
1372
|
+
getHandler({ "msg-drain-1": msg1 }),
|
|
1373
|
+
]);
|
|
1374
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1375
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1376
|
+
|
|
1377
|
+
// Start with a cursor that has a stale pageToken from a prior cap-hit pass.
|
|
1378
|
+
const cursor: ConnectorCursor = {
|
|
1379
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1380
|
+
value: JSON.stringify({
|
|
1381
|
+
watermarkMs: String(Number(T1) - 1000),
|
|
1382
|
+
skippedIds: {},
|
|
1383
|
+
seenIds: {},
|
|
1384
|
+
pageToken: "stale-page-token-to-be-cleared",
|
|
1385
|
+
}),
|
|
1386
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
const result = await connector.syncIncremental({ cursor, config });
|
|
1390
|
+
|
|
1391
|
+
// One message imported.
|
|
1392
|
+
assert.equal(result.newDocs.length, 1);
|
|
1393
|
+
|
|
1394
|
+
// The next cursor must NOT contain a pageToken (window fully drained).
|
|
1395
|
+
const payload = JSON.parse(result.nextCursor.value) as {
|
|
1396
|
+
pageToken?: string;
|
|
1397
|
+
watermarkMs: string;
|
|
1398
|
+
};
|
|
1399
|
+
assert.equal(
|
|
1400
|
+
payload.pageToken,
|
|
1401
|
+
undefined,
|
|
1402
|
+
"pageToken must be cleared from cursor when the after: window is fully drained",
|
|
1403
|
+
);
|
|
1404
|
+
// Watermark must also advance.
|
|
1405
|
+
assert.equal(Number(payload.watermarkMs), Number(T2));
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
// ---------------------------------------------------------------------------
|
|
1409
|
+
// Thread 3: shared isTransientHttpError helper
|
|
1410
|
+
// ---------------------------------------------------------------------------
|
|
1411
|
+
|
|
1412
|
+
test("isTransientHttpError classifies transient and terminal HTTP errors (Thread 3)", () => {
|
|
1413
|
+
// Transient — re-throw.
|
|
1414
|
+
assert.equal(isTransientHttpError({ status: 429 }), true);
|
|
1415
|
+
assert.equal(isTransientHttpError({ status: 500 }), true);
|
|
1416
|
+
assert.equal(isTransientHttpError({ status: 503 }), true);
|
|
1417
|
+
assert.equal(isTransientHttpError({ response: { status: 429 } }), true);
|
|
1418
|
+
assert.equal(isTransientHttpError({ response: { status: 502 } }), true);
|
|
1419
|
+
assert.equal(isTransientHttpError({ name: "AbortError" }), true);
|
|
1420
|
+
assert.equal(isTransientHttpError({ code: "ECONNRESET" }), true);
|
|
1421
|
+
assert.equal(isTransientHttpError({ code: "ETIMEDOUT" }), true);
|
|
1422
|
+
assert.equal(isTransientHttpError({ code: "ENOTFOUND" }), true);
|
|
1423
|
+
assert.equal(isTransientHttpError({ code: "EAI_AGAIN" }), true);
|
|
1424
|
+
assert.equal(isTransientHttpError(new Error("plain network error")), true);
|
|
1425
|
+
|
|
1426
|
+
// Terminal — skip-and-continue.
|
|
1427
|
+
assert.equal(isTransientHttpError({ status: 404 }), false);
|
|
1428
|
+
assert.equal(isTransientHttpError({ response: { status: 403 } }), false);
|
|
1429
|
+
assert.equal(isTransientHttpError({ response: { status: 400 } }), false);
|
|
1430
|
+
assert.equal(isTransientHttpError({ response: { status: 410 } }), false);
|
|
1431
|
+
|
|
1432
|
+
// Non-objects.
|
|
1433
|
+
assert.equal(isTransientHttpError(null), false);
|
|
1434
|
+
assert.equal(isTransientHttpError(undefined), false);
|
|
1435
|
+
assert.equal(isTransientHttpError("error string"), false);
|
|
1436
|
+
assert.equal(isTransientHttpError(42), false);
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
test("isTransientHttpError resolves connector-specific statusProps (Thread 3)", () => {
|
|
1440
|
+
// gmailStatus (used by Gmail connector).
|
|
1441
|
+
assert.equal(isTransientHttpError({ gmailStatus: 429 }, ["gmailStatus"]), true);
|
|
1442
|
+
assert.equal(isTransientHttpError({ gmailStatus: 503 }, ["gmailStatus"]), true);
|
|
1443
|
+
assert.equal(isTransientHttpError({ gmailStatus: 404 }, ["gmailStatus"]), false);
|
|
1444
|
+
|
|
1445
|
+
// notionStatus (used by Notion connector).
|
|
1446
|
+
assert.equal(isTransientHttpError({ notionStatus: 429 }, ["notionStatus"]), true);
|
|
1447
|
+
assert.equal(isTransientHttpError({ notionStatus: 500 }, ["notionStatus"]), true);
|
|
1448
|
+
assert.equal(isTransientHttpError({ notionStatus: 403 }, ["notionStatus"]), false);
|
|
1449
|
+
|
|
1450
|
+
// statusProps takes priority over generic `status` field.
|
|
1451
|
+
// Object has generic status=200 (terminal) but customStatus=503 (transient).
|
|
1452
|
+
assert.equal(isTransientHttpError({ status: 200, customStatus: 503 }, ["customStatus"]), true);
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
test("isTransientGmailError delegates to isTransientHttpError with gmailStatus (Thread 3)", () => {
|
|
1456
|
+
// Verify that isTransientGmailError and isTransientHttpError give the same
|
|
1457
|
+
// results for the same inputs, confirming delegation is working correctly.
|
|
1458
|
+
const cases: Array<[unknown, boolean]> = [
|
|
1459
|
+
[{ gmailStatus: 429 }, true],
|
|
1460
|
+
[{ gmailStatus: 503 }, true],
|
|
1461
|
+
[{ gmailStatus: 404 }, false],
|
|
1462
|
+
[{ response: { status: 429 } }, true],
|
|
1463
|
+
[{ code: "ECONNRESET" }, true],
|
|
1464
|
+
[{ name: "AbortError" }, true],
|
|
1465
|
+
[null, false],
|
|
1466
|
+
];
|
|
1467
|
+
for (const [input, expected] of cases) {
|
|
1468
|
+
assert.equal(
|
|
1469
|
+
isTransientGmailError(input),
|
|
1470
|
+
expected,
|
|
1471
|
+
`isTransientGmailError(${JSON.stringify(input)}) should be ${expected}`,
|
|
1472
|
+
);
|
|
1473
|
+
assert.equal(
|
|
1474
|
+
isTransientHttpError(input, ["gmailStatus"]),
|
|
1475
|
+
expected,
|
|
1476
|
+
`isTransientHttpError(${JSON.stringify(input)}, ["gmailStatus"]) should be ${expected}`,
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
// ---------------------------------------------------------------------------
|
|
1482
|
+
// pruneSeenIds — seenIds bounding and date-based pruning
|
|
1483
|
+
// (Codex P1 PRRT_kwDORJXyws59se73)
|
|
1484
|
+
// ---------------------------------------------------------------------------
|
|
1485
|
+
|
|
1486
|
+
test("pruneSeenIds removes entries that cannot be returned by after:floor(watermarkMs/1000)", () => {
|
|
1487
|
+
// watermarkMs = 1745000000500 → floorSecBoundaryMs = 1745000000000
|
|
1488
|
+
// after:1745000000 returns messages with internalDate > 1745000000000
|
|
1489
|
+
// So messages AT or BELOW the floor-second boundary are pruned; above are kept.
|
|
1490
|
+
const seenIds: Record<string, string> = {
|
|
1491
|
+
"msg-at-floor": "1745000000000", // exactly at floor boundary → pruned (not strictly above)
|
|
1492
|
+
"msg-above-1": "1745000000100", // strictly above floor boundary → retained
|
|
1493
|
+
"msg-above-2": "1745000000300", // retained
|
|
1494
|
+
"msg-at-wmark": "1745000000500", // at watermark → retained
|
|
1495
|
+
"msg-new-1": "1745000000600", // retained
|
|
1496
|
+
"msg-new-2": "1745000000900", // retained
|
|
1497
|
+
};
|
|
1498
|
+
const result = pruneSeenIds(seenIds, 1745000000500);
|
|
1499
|
+
assert.equal(result["msg-at-floor"], undefined, "entry at floor boundary must be pruned (not strictly above it)");
|
|
1500
|
+
assert.equal(result["msg-above-1"], "1745000000100", "entry above floor boundary must be retained");
|
|
1501
|
+
assert.equal(result["msg-above-2"], "1745000000300", "entry above floor boundary must be retained");
|
|
1502
|
+
assert.equal(result["msg-at-wmark"], "1745000000500", "entry at watermark must be retained");
|
|
1503
|
+
assert.equal(result["msg-new-1"], "1745000000600", "entry above watermark must be retained");
|
|
1504
|
+
assert.equal(result["msg-new-2"], "1745000000900", "entry above watermark must be retained");
|
|
1505
|
+
assert.equal(Object.keys(result).length, 5);
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
test("pruneSeenIds prunes entries from a previous second (different second boundary)", () => {
|
|
1509
|
+
// watermarkMs = 1745000002500 → floorSecBoundaryMs = 1745000002000
|
|
1510
|
+
// Messages in second 1745000001 (all <= 1745000002000) are pruned.
|
|
1511
|
+
// Messages in second 1745000002 that are > 1745000002000 are kept.
|
|
1512
|
+
const seenIds: Record<string, string> = {
|
|
1513
|
+
"msg-prev-sec": "1745000001500", // second 1745000001 → pruned (< floor boundary)
|
|
1514
|
+
"msg-at-sec2": "1745000002000", // exactly at floor boundary → pruned
|
|
1515
|
+
"msg-in-sec2": "1745000002300", // in second 2, above floor → retained
|
|
1516
|
+
"msg-at-wmark": "1745000002500", // at watermark → retained
|
|
1517
|
+
};
|
|
1518
|
+
const result = pruneSeenIds(seenIds, 1745000002500);
|
|
1519
|
+
assert.equal(result["msg-prev-sec"], undefined, "message from previous second must be pruned");
|
|
1520
|
+
assert.equal(result["msg-at-sec2"], undefined, "message exactly at floor boundary must be pruned");
|
|
1521
|
+
assert.equal(result["msg-in-sec2"], "1745000002300", "message in current second above floor must be retained");
|
|
1522
|
+
assert.equal(result["msg-at-wmark"], "1745000002500", "message at watermark must be retained");
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
test("pruneSeenIds with empty seenIds returns empty map", () => {
|
|
1526
|
+
assert.deepEqual(pruneSeenIds({}, 1745000000000), {});
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
test("pruneSeenIds retains all entries when all are in the same floor-second as watermark", () => {
|
|
1530
|
+
// watermarkMs = 1745000000500 → floorSec = 1745000000000
|
|
1531
|
+
// All entries at 1745000000100+ are strictly above the floor boundary → retained.
|
|
1532
|
+
const seenIds: Record<string, string> = {
|
|
1533
|
+
"msg-a": "1745000000100",
|
|
1534
|
+
"msg-b": "1745000000900",
|
|
1535
|
+
};
|
|
1536
|
+
assert.deepEqual(pruneSeenIds(seenIds, 1745000000500), seenIds);
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
test("pruneSeenIds enforces hard cap: evicts to SEEN_IDS_RETAIN when count exceeds SEEN_IDS_MAX", () => {
|
|
1540
|
+
const seenIds: Record<string, string> = {};
|
|
1541
|
+
for (let i = 0; i < SEEN_IDS_MAX + 10; i++) {
|
|
1542
|
+
seenIds[`msg-cap-${i}`] = String(1_000_000 + i); // all >= watermark=0
|
|
1543
|
+
}
|
|
1544
|
+
const result = pruneSeenIds(seenIds, 0);
|
|
1545
|
+
assert.equal(
|
|
1546
|
+
Object.keys(result).length,
|
|
1547
|
+
SEEN_IDS_RETAIN,
|
|
1548
|
+
`after cap eviction, count must equal SEEN_IDS_RETAIN (${SEEN_IDS_RETAIN})`,
|
|
1549
|
+
);
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
test("pruneSeenIds cap eviction retains the most recent entries (highest internalDate)", () => {
|
|
1553
|
+
const seenIds: Record<string, string> = {};
|
|
1554
|
+
const totalEntries = SEEN_IDS_MAX + 50;
|
|
1555
|
+
for (let i = 0; i < totalEntries; i++) {
|
|
1556
|
+
seenIds[`msg-cap-${i}`] = String(2_000_000 + i);
|
|
1557
|
+
}
|
|
1558
|
+
const result = pruneSeenIds(seenIds, 0);
|
|
1559
|
+
assert.equal(Object.keys(result).length, SEEN_IDS_RETAIN);
|
|
1560
|
+
const lowestRetained = 2_000_000 + (totalEntries - SEEN_IDS_RETAIN);
|
|
1561
|
+
for (const [, dateMs] of Object.entries(result)) {
|
|
1562
|
+
assert.ok(Number(dateMs) >= lowestRetained, `retained entry ${dateMs} must be >= ${lowestRetained}`);
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
test("pruneSeenIds does not evict when below cap", () => {
|
|
1567
|
+
const seenIds: Record<string, string> = {};
|
|
1568
|
+
const count = SEEN_IDS_MAX - 1;
|
|
1569
|
+
for (let i = 0; i < count; i++) {
|
|
1570
|
+
seenIds[`msg-small-${i}`] = String(1_000_000 + i);
|
|
1571
|
+
}
|
|
1572
|
+
const result = pruneSeenIds(seenIds, 0);
|
|
1573
|
+
assert.equal(Object.keys(result).length, count, "no eviction when below cap");
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
// ---------------------------------------------------------------------------
|
|
1577
|
+
// Codex P1 PRRT_kwDORJXyws59sh5I + Cursor PRRT_kwDORJXyws59sji9:
|
|
1578
|
+
// Cap-hit mid-page must save the CURRENT page token, not the next page token.
|
|
1579
|
+
// ---------------------------------------------------------------------------
|
|
1580
|
+
|
|
1581
|
+
test("cursor saves the CURRENT page token when cap is hit mid-page (not next page token)", async () => {
|
|
1582
|
+
// Generate exactly MAX_MESSAGES_PER_PASS + 1 messages on one page so the
|
|
1583
|
+
// cap fires mid-page. The response also advertises nextPageToken="next-page".
|
|
1584
|
+
// With the fix, the saved cursor.pageToken must equal "current-page-token"
|
|
1585
|
+
// (the token used to fetch this page), NOT "next-page" (the next page).
|
|
1586
|
+
const baseMs = Number(T1);
|
|
1587
|
+
const msgRefs: GmailMessageRef[] = [];
|
|
1588
|
+
const msgMap: Record<string, GmailMessage> = {};
|
|
1589
|
+
for (let i = 0; i < MAX_MESSAGES_PER_PASS; i++) {
|
|
1590
|
+
const id = `msg-cpt-${i}`;
|
|
1591
|
+
msgRefs.push({ id });
|
|
1592
|
+
msgMap[id] = makeMessage(id, String(baseMs + i), `body ${i}`);
|
|
1593
|
+
}
|
|
1594
|
+
// One extra message to trigger the cap.
|
|
1595
|
+
msgRefs.push({ id: "msg-cpt-extra" });
|
|
1596
|
+
msgMap["msg-cpt-extra"] = makeMessage("msg-cpt-extra", String(baseMs + MAX_MESSAGES_PER_PASS), "extra");
|
|
1597
|
+
|
|
1598
|
+
const fetchFn = makeFetch([
|
|
1599
|
+
tokenHandler(),
|
|
1600
|
+
{
|
|
1601
|
+
match: (url) => url.includes("/messages") && !url.includes("format=full"),
|
|
1602
|
+
respond: () => ({
|
|
1603
|
+
status: 200,
|
|
1604
|
+
data: { messages: msgRefs, nextPageToken: "next-page" },
|
|
1605
|
+
}),
|
|
1606
|
+
},
|
|
1607
|
+
getHandler(msgMap),
|
|
1608
|
+
]);
|
|
1609
|
+
|
|
1610
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1611
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1612
|
+
// Cursor with a prior pageToken ("current-page-token").
|
|
1613
|
+
const cursor: ConnectorCursor = {
|
|
1614
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1615
|
+
value: JSON.stringify({
|
|
1616
|
+
watermarkMs: String(Number(T1) - 1000),
|
|
1617
|
+
skippedIds: {},
|
|
1618
|
+
seenIds: {},
|
|
1619
|
+
pageToken: "current-page-token",
|
|
1620
|
+
}),
|
|
1621
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
const result = await connector.syncIncremental({ cursor, config });
|
|
1625
|
+
assert.equal(result.newDocs.length, MAX_MESSAGES_PER_PASS, "must process exactly MAX_MESSAGES_PER_PASS messages");
|
|
1626
|
+
|
|
1627
|
+
const payload = JSON.parse(result.nextCursor.value) as { pageToken?: string };
|
|
1628
|
+
assert.equal(
|
|
1629
|
+
payload.pageToken,
|
|
1630
|
+
"current-page-token",
|
|
1631
|
+
"when cap hits mid-page, cursor must save the CURRENT page token so next poll re-fetches this page",
|
|
1632
|
+
);
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
test("cursor saves undefined pageToken when cap is hit on page 1 (no prior pageToken)", async () => {
|
|
1636
|
+
const baseMs = Number(T1);
|
|
1637
|
+
const msgRefs: GmailMessageRef[] = [];
|
|
1638
|
+
const msgMap: Record<string, GmailMessage> = {};
|
|
1639
|
+
for (let i = 0; i < MAX_MESSAGES_PER_PASS; i++) {
|
|
1640
|
+
const id = `msg-p1cap-${i}`;
|
|
1641
|
+
msgRefs.push({ id });
|
|
1642
|
+
msgMap[id] = makeMessage(id, String(baseMs + i), `body ${i}`);
|
|
1643
|
+
}
|
|
1644
|
+
msgRefs.push({ id: "msg-p1cap-extra" });
|
|
1645
|
+
msgMap["msg-p1cap-extra"] = makeMessage("msg-p1cap-extra", String(baseMs + MAX_MESSAGES_PER_PASS), "extra");
|
|
1646
|
+
|
|
1647
|
+
const fetchFn = makeFetch([
|
|
1648
|
+
tokenHandler(),
|
|
1649
|
+
{
|
|
1650
|
+
match: (url) => url.includes("/messages") && !url.includes("format=full"),
|
|
1651
|
+
respond: () => ({
|
|
1652
|
+
status: 200,
|
|
1653
|
+
data: { messages: msgRefs, nextPageToken: "hypothetical-next" },
|
|
1654
|
+
}),
|
|
1655
|
+
},
|
|
1656
|
+
getHandler(msgMap),
|
|
1657
|
+
]);
|
|
1658
|
+
|
|
1659
|
+
const connector = createGmailConnector({ fetchFn });
|
|
1660
|
+
const config = connector.validateConfig({ ...SYNTHETIC_CREDS });
|
|
1661
|
+
// No prior pageToken (page 1 from the start).
|
|
1662
|
+
const cursor: ConnectorCursor = {
|
|
1663
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1664
|
+
value: JSON.stringify({ watermarkMs: String(Number(T1) - 1000), skippedIds: {}, seenIds: {} }),
|
|
1665
|
+
updatedAt: "2026-04-25T00:00:00.000Z",
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1668
|
+
const result = await connector.syncIncremental({ cursor, config });
|
|
1669
|
+
assert.equal(result.newDocs.length, MAX_MESSAGES_PER_PASS);
|
|
1670
|
+
|
|
1671
|
+
const payload = JSON.parse(result.nextCursor.value) as { pageToken?: string };
|
|
1672
|
+
assert.equal(
|
|
1673
|
+
payload.pageToken,
|
|
1674
|
+
undefined,
|
|
1675
|
+
"when cap hits on page 1, cursor pageToken must be undefined so next poll re-fetches from page 1",
|
|
1676
|
+
);
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
// ---------------------------------------------------------------------------
|
|
1680
|
+
// Codex P2 PRRT_kwDORJXyws59se75: validateGmailConfig rejects NaN / non-numeric
|
|
1681
|
+
// Per CLAUDE.md gotcha #51: invalid values must throw, not silently default.
|
|
1682
|
+
// ---------------------------------------------------------------------------
|
|
1683
|
+
|
|
1684
|
+
test("validateGmailConfig rejects non-numeric string pollIntervalMs (Codex P2)", () => {
|
|
1685
|
+
assert.throws(
|
|
1686
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, pollIntervalMs: "abc" }),
|
|
1687
|
+
/pollIntervalMs/,
|
|
1688
|
+
"non-numeric string pollIntervalMs must be rejected explicitly",
|
|
1689
|
+
);
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1692
|
+
test("validateGmailConfig rejects NaN pollIntervalMs (Codex P2)", () => {
|
|
1693
|
+
assert.throws(
|
|
1694
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, pollIntervalMs: NaN }),
|
|
1695
|
+
/pollIntervalMs/,
|
|
1696
|
+
"NaN pollIntervalMs must be rejected explicitly",
|
|
1697
|
+
);
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
test("validateGmailConfig rejects Infinity pollIntervalMs (Codex P2)", () => {
|
|
1701
|
+
assert.throws(
|
|
1702
|
+
() => validateGmailConfig({ ...SYNTHETIC_CREDS, pollIntervalMs: Infinity }),
|
|
1703
|
+
/pollIntervalMs/,
|
|
1704
|
+
"Infinity pollIntervalMs must be rejected explicitly",
|
|
1705
|
+
);
|
|
1706
|
+
});
|