@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.
Files changed (1462) hide show
  1. package/README.md +3 -3
  2. package/dist/access-cli.d.ts +2 -1
  3. package/dist/access-cli.js +293 -104
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +31 -62
  6. package/dist/access-http.js +53 -35
  7. package/dist/access-mcp.d.ts +31 -8
  8. package/dist/access-mcp.js +45 -34
  9. package/dist/access-schema.d.ts +197 -14
  10. package/dist/access-schema.js +16 -5
  11. package/dist/access-service-DcCDmNYC.d.ts +1542 -0
  12. package/dist/access-service.d.ts +30 -9
  13. package/dist/access-service.js +42 -32
  14. package/dist/action-confidence.d.ts +83 -0
  15. package/dist/action-confidence.js +22 -0
  16. package/dist/active-memory-bridge.d.ts +1 -1
  17. package/dist/active-memory-bridge.js +2 -2
  18. package/dist/active-recall.d.ts +1 -1
  19. package/dist/active-recall.js +11 -3
  20. package/dist/active-recall.js.map +1 -1
  21. package/dist/adapters/claude-code.d.ts +24 -0
  22. package/dist/adapters/claude-code.js +9 -0
  23. package/dist/adapters/codex.d.ts +25 -0
  24. package/dist/adapters/codex.js +9 -0
  25. package/dist/adapters/hermes.d.ts +35 -0
  26. package/dist/adapters/hermes.js +9 -0
  27. package/dist/adapters/index.d.ts +6 -0
  28. package/dist/adapters/index.js +26 -0
  29. package/dist/adapters/registry.d.ts +20 -0
  30. package/dist/adapters/registry.js +13 -0
  31. package/dist/adapters/replit.d.ts +28 -0
  32. package/dist/adapters/replit.js +9 -0
  33. package/dist/adapters/types.d.ts +43 -0
  34. package/dist/adapters/types.js +8 -0
  35. package/dist/behavior-learner.d.ts +1 -1
  36. package/dist/behavior-signals.d.ts +1 -1
  37. package/dist/bootstrap.d.ts +23 -6
  38. package/dist/boxes.d.ts +7 -0
  39. package/dist/boxes.js +1 -1
  40. package/dist/briefing.d.ts +5 -3
  41. package/dist/briefing.js +10 -7
  42. package/dist/buffer-surprise-report.d.ts +1 -1
  43. package/dist/buffer-surprise-report.js +1 -1
  44. package/dist/buffer.d.ts +18 -4
  45. package/dist/buffer.js +1 -1
  46. package/dist/calibration.d.ts +1 -1
  47. package/dist/calibration.js +6 -6
  48. package/dist/capsule-cli.d.ts +4 -4
  49. package/dist/capsule-cli.js +1 -1
  50. package/dist/capsule-crypto-5CYAGVC5.js +18 -0
  51. package/dist/capsule-merge-4MGKE7C5.js +189 -0
  52. package/dist/causal-behavior.d.ts +9 -29
  53. package/dist/causal-behavior.js +6 -3
  54. package/dist/causal-behavior.js.map +1 -1
  55. package/dist/causal-chain.js +3 -2
  56. package/dist/causal-consolidation.d.ts +2 -2
  57. package/dist/causal-consolidation.js +28 -17
  58. package/dist/causal-consolidation.js.map +1 -1
  59. package/dist/causal-retrieval.js +3 -3
  60. package/dist/causal-trajectory.js +1 -1
  61. package/dist/chunk-25MQ7IHJ.js +427 -0
  62. package/dist/chunk-25MQ7IHJ.js.map +1 -0
  63. package/dist/chunk-2F2W355T.js +256 -0
  64. package/dist/chunk-2F2W355T.js.map +1 -0
  65. package/dist/chunk-2KI4QFHU.js +228 -0
  66. package/dist/chunk-2KI4QFHU.js.map +1 -0
  67. package/dist/chunk-2PRQG7PV.js +86 -0
  68. package/dist/chunk-2PRQG7PV.js.map +1 -0
  69. package/dist/chunk-2QR3XXIC.js +2272 -0
  70. package/dist/chunk-2QR3XXIC.js.map +1 -0
  71. package/dist/chunk-2WWLHTZY.js +121 -0
  72. package/dist/chunk-326G7DJK.js +2185 -0
  73. package/dist/chunk-326G7DJK.js.map +1 -0
  74. package/dist/chunk-34DQE4KF.js +174 -0
  75. package/dist/chunk-34DQE4KF.js.map +1 -0
  76. package/dist/chunk-3APJ5EVB.js +601 -0
  77. package/dist/chunk-3APJ5EVB.js.map +1 -0
  78. package/dist/chunk-3HPAPHUK.js +51 -0
  79. package/dist/chunk-3HPAPHUK.js.map +1 -0
  80. package/dist/chunk-3JXBXXM2.js +69 -0
  81. package/dist/chunk-3JXBXXM2.js.map +1 -0
  82. package/dist/chunk-3KW65B36.js +681 -0
  83. package/dist/chunk-3KW65B36.js.map +1 -0
  84. package/dist/chunk-3UXOZBHV.js +20 -0
  85. package/dist/chunk-3UXOZBHV.js.map +1 -0
  86. package/dist/chunk-3VAL7ZL2.js +266 -0
  87. package/dist/chunk-3VAL7ZL2.js.map +1 -0
  88. package/dist/chunk-3Y4P7RXM.js +31 -0
  89. package/dist/chunk-3Y4P7RXM.js.map +1 -0
  90. package/dist/chunk-47VWKCAF.js +273 -0
  91. package/dist/chunk-47VWKCAF.js.map +1 -0
  92. package/dist/chunk-4CRG46BG.js +271 -0
  93. package/dist/chunk-4RA3C3EV.js +60 -0
  94. package/dist/chunk-4RA3C3EV.js.map +1 -0
  95. package/dist/chunk-5375UYTQ.js +914 -0
  96. package/dist/chunk-5375UYTQ.js.map +1 -0
  97. package/dist/chunk-56K5QLHX.js +506 -0
  98. package/dist/chunk-56K5QLHX.js.map +1 -0
  99. package/dist/chunk-5NXIJZFX.js +180 -0
  100. package/dist/chunk-5NXIJZFX.js.map +1 -0
  101. package/dist/chunk-5RGLBDQF.js +596 -0
  102. package/dist/chunk-5RGLBDQF.js.map +1 -0
  103. package/dist/chunk-5UZXUTVO.js +9 -0
  104. package/dist/chunk-5UZXUTVO.js.map +1 -0
  105. package/dist/chunk-65PG43EQ.js +105 -0
  106. package/dist/chunk-65PG43EQ.js.map +1 -0
  107. package/dist/chunk-66DHUKLO.js +57 -0
  108. package/dist/chunk-66DHUKLO.js.map +1 -0
  109. package/dist/chunk-6FC5EGNV.js +46 -0
  110. package/dist/chunk-6FC5EGNV.js.map +1 -0
  111. package/dist/chunk-6H2TESSP.js +62 -0
  112. package/dist/chunk-6H2TESSP.js.map +1 -0
  113. package/dist/chunk-6LVVDPJ4.js +32 -0
  114. package/dist/chunk-6LVVDPJ4.js.map +1 -0
  115. package/dist/chunk-6NKAQ74D.js +2237 -0
  116. package/dist/chunk-6NKAQ74D.js.map +1 -0
  117. package/dist/chunk-6RVI47ZR.js +159 -0
  118. package/dist/chunk-6RVI47ZR.js.map +1 -0
  119. package/dist/chunk-7AAT6G4Q.js +5117 -0
  120. package/dist/chunk-7AAT6G4Q.js.map +1 -0
  121. package/dist/chunk-7DTASS5T.js +29 -0
  122. package/dist/chunk-7DTASS5T.js.map +1 -0
  123. package/dist/chunk-7IASACLB.js +596 -0
  124. package/dist/chunk-7MNMYOFP.js +32 -0
  125. package/dist/chunk-7MNMYOFP.js.map +1 -0
  126. package/dist/chunk-7N4KAIGN.js +133 -0
  127. package/dist/chunk-7N4KAIGN.js.map +1 -0
  128. package/dist/chunk-7OZ53EXP.js +101 -0
  129. package/dist/chunk-7OZ53EXP.js.map +1 -0
  130. package/dist/chunk-7XYTQGCC.js +134 -0
  131. package/dist/chunk-7XYTQGCC.js.map +1 -0
  132. package/dist/chunk-A2XUIMJ3.js +341 -0
  133. package/dist/chunk-A2XUIMJ3.js.map +1 -0
  134. package/dist/chunk-AC5LO7IU.js +308 -0
  135. package/dist/chunk-AC5LO7IU.js.map +1 -0
  136. package/dist/chunk-AGZQD76C.js +201 -0
  137. package/dist/chunk-AGZQD76C.js.map +1 -0
  138. package/dist/chunk-AH2JUU6X.js +336 -0
  139. package/dist/chunk-AH2JUU6X.js.map +1 -0
  140. package/dist/chunk-APO3DCMU.js +361 -0
  141. package/dist/chunk-APO3DCMU.js.map +1 -0
  142. package/dist/chunk-BFBF3XEF.js +283 -0
  143. package/dist/chunk-BFBF3XEF.js.map +1 -0
  144. package/dist/chunk-BJ3KMYTB.js +1974 -0
  145. package/dist/chunk-BJ3KMYTB.js.map +1 -0
  146. package/dist/chunk-C5BCH4ZS.js +317 -0
  147. package/dist/chunk-C5BCH4ZS.js.map +1 -0
  148. package/dist/chunk-CHEL3SKB.js +6758 -0
  149. package/dist/chunk-CHEL3SKB.js.map +1 -0
  150. package/dist/chunk-CQZRLNMV.js +1491 -0
  151. package/dist/chunk-CQZRLNMV.js.map +1 -0
  152. package/dist/chunk-D46YSIYX.js +892 -0
  153. package/dist/chunk-D46YSIYX.js.map +1 -0
  154. package/dist/chunk-DB5A3NHS.js +906 -0
  155. package/dist/chunk-DB5A3NHS.js.map +1 -0
  156. package/dist/chunk-DINWEURR.js +648 -0
  157. package/dist/chunk-DINWEURR.js.map +1 -0
  158. package/dist/chunk-DK5LDEQM.js +530 -0
  159. package/dist/chunk-DK5LDEQM.js.map +1 -0
  160. package/dist/chunk-DOM4GKSW.js +34 -0
  161. package/dist/chunk-DOM4GKSW.js.map +1 -0
  162. package/dist/chunk-EDTHC6UD.js +1075 -0
  163. package/dist/chunk-EDTHC6UD.js.map +1 -0
  164. package/dist/chunk-EFJ3MQ4V.js +721 -0
  165. package/dist/chunk-EHRTFRWW.js +89 -0
  166. package/dist/chunk-EHRTFRWW.js.map +1 -0
  167. package/dist/chunk-FAJ7FZYM.js +11 -0
  168. package/dist/chunk-FAJ7FZYM.js.map +1 -0
  169. package/dist/chunk-FBYESMQ2.js +570 -0
  170. package/dist/chunk-FBYESMQ2.js.map +1 -0
  171. package/dist/chunk-FDU6HUUL.js +147 -0
  172. package/dist/chunk-FF4KLI5W.js +99 -0
  173. package/dist/chunk-FF4KLI5W.js.map +1 -0
  174. package/dist/chunk-FIT6DMX6.js +310 -0
  175. package/dist/chunk-FIT6DMX6.js.map +1 -0
  176. package/dist/chunk-FJ43PRLT.js +272 -0
  177. package/dist/chunk-FJ43PRLT.js.map +1 -0
  178. package/dist/chunk-FKFMOY3N.js +32 -0
  179. package/dist/chunk-FKFMOY3N.js.map +1 -0
  180. package/dist/chunk-FLTNHQK6.js +262 -0
  181. package/dist/chunk-FLTNHQK6.js.map +1 -0
  182. package/dist/chunk-GA454ALV.js +12436 -0
  183. package/dist/chunk-GA454ALV.js.map +1 -0
  184. package/dist/chunk-GGKRUQOO.js +228 -0
  185. package/dist/chunk-GIF42EW3.js +63 -0
  186. package/dist/chunk-GIF42EW3.js.map +1 -0
  187. package/dist/chunk-GL6I6MEQ.js +647 -0
  188. package/dist/chunk-H3ME6L6D.js +709 -0
  189. package/dist/chunk-H3ME6L6D.js.map +1 -0
  190. package/dist/chunk-HHLLAQGZ.js +1 -0
  191. package/dist/chunk-HXXBL2KD.js +2040 -0
  192. package/dist/chunk-I5V2VDIW.js +219 -0
  193. package/dist/chunk-I5V2VDIW.js.map +1 -0
  194. package/dist/chunk-I6K5FBRQ.js +35 -0
  195. package/dist/chunk-I6K5FBRQ.js.map +1 -0
  196. package/dist/chunk-ICRIXAP2.js +121 -0
  197. package/dist/chunk-ICRIXAP2.js.map +1 -0
  198. package/dist/chunk-J4EB7DNW.js +11 -0
  199. package/dist/chunk-J4EB7DNW.js.map +1 -0
  200. package/dist/chunk-JLFA7DQG.js +62 -0
  201. package/dist/chunk-JLFA7DQG.js.map +1 -0
  202. package/dist/chunk-KJTKLXTH.js +9 -0
  203. package/dist/chunk-KJTKLXTH.js.map +1 -0
  204. package/dist/chunk-KLAO5DGL.js +917 -0
  205. package/dist/chunk-KLAO5DGL.js.map +1 -0
  206. package/dist/chunk-KNKUID7G.js +183 -0
  207. package/dist/chunk-KOSORCJG.js +624 -0
  208. package/dist/chunk-KOSORCJG.js.map +1 -0
  209. package/dist/chunk-KUJVMMZQ.js +1262 -0
  210. package/dist/chunk-KUJVMMZQ.js.map +1 -0
  211. package/dist/chunk-LCR46JY5.js +123 -0
  212. package/dist/chunk-LCR46JY5.js.map +1 -0
  213. package/dist/chunk-LLQ2LLWF.js +148 -0
  214. package/dist/chunk-LLQ2LLWF.js.map +1 -0
  215. package/dist/chunk-LPMVBPA3.js +236 -0
  216. package/dist/chunk-LT3NLYSI.js +50 -0
  217. package/dist/chunk-LT3NLYSI.js.map +1 -0
  218. package/dist/chunk-LUDTDZLK.js +287 -0
  219. package/dist/chunk-LUDTDZLK.js.map +1 -0
  220. package/dist/chunk-M23FSH32.js +3963 -0
  221. package/dist/chunk-M23FSH32.js.map +1 -0
  222. package/dist/chunk-MC26UJIM.js +118 -0
  223. package/dist/chunk-ME6ESPZU.js +119 -0
  224. package/dist/chunk-ME6ESPZU.js.map +1 -0
  225. package/dist/chunk-MGKYQQYF.js +272 -0
  226. package/dist/chunk-MGKYQQYF.js.map +1 -0
  227. package/dist/chunk-MJFNCJXV.js +66 -0
  228. package/dist/chunk-MJFNCJXV.js.map +1 -0
  229. package/dist/chunk-MSWG7JI6.js +237 -0
  230. package/dist/chunk-MSWG7JI6.js.map +1 -0
  231. package/dist/chunk-MT25YHYH.js +141 -0
  232. package/dist/chunk-MT25YHYH.js.map +1 -0
  233. package/dist/chunk-MT4HVDUZ.js +53 -0
  234. package/dist/chunk-MY6TPVXW.js +219 -0
  235. package/dist/chunk-N2D6GXBM.js +267 -0
  236. package/dist/chunk-N2D6GXBM.js.map +1 -0
  237. package/dist/chunk-NJ3MJQZX.js +46 -0
  238. package/dist/chunk-NJ3MJQZX.js.map +1 -0
  239. package/dist/chunk-NMZY542O.js +335 -0
  240. package/dist/chunk-NMZY542O.js.map +1 -0
  241. package/dist/chunk-NNVTUXEB.js +23 -0
  242. package/dist/chunk-NZL6GGQE.js +375 -0
  243. package/dist/chunk-NZL6GGQE.js.map +1 -0
  244. package/dist/chunk-OAZ5MFUB.js +4124 -0
  245. package/dist/chunk-OAZ5MFUB.js.map +1 -0
  246. package/dist/chunk-OIGNEXKZ.js +237 -0
  247. package/dist/chunk-OIGNEXKZ.js.map +1 -0
  248. package/dist/chunk-OZKZ2TRP.js +3729 -0
  249. package/dist/chunk-OZKZ2TRP.js.map +1 -0
  250. package/dist/chunk-P4NEIHUT.js +108 -0
  251. package/dist/chunk-P7FMDTKL.js +103 -0
  252. package/dist/chunk-P7FMDTKL.js.map +1 -0
  253. package/dist/chunk-PD6O7AXF.js +110 -0
  254. package/dist/chunk-PD6O7AXF.js.map +1 -0
  255. package/dist/chunk-PHK3HARR.js +32 -0
  256. package/dist/chunk-PHK3HARR.js.map +1 -0
  257. package/dist/chunk-PIRJPV5T.js +98 -0
  258. package/dist/chunk-PIRJPV5T.js.map +1 -0
  259. package/dist/chunk-PK7H5L6Y.js +159 -0
  260. package/dist/chunk-PK7H5L6Y.js.map +1 -0
  261. package/dist/chunk-PR5FBTFU.js +233 -0
  262. package/dist/chunk-PR5FBTFU.js.map +1 -0
  263. package/dist/chunk-PU63GXWS.js +174 -0
  264. package/dist/chunk-PU63GXWS.js.map +1 -0
  265. package/dist/chunk-PYPOFEMK.js +294 -0
  266. package/dist/chunk-PYPOFEMK.js.map +1 -0
  267. package/dist/chunk-PZIAX57I.js +124 -0
  268. package/dist/chunk-PZIAX57I.js.map +1 -0
  269. package/dist/chunk-Q7P4WJDP.js +26 -0
  270. package/dist/chunk-Q7P4WJDP.js.map +1 -0
  271. package/dist/chunk-QDZ2RLEC.js +908 -0
  272. package/dist/chunk-QDZ2RLEC.js.map +1 -0
  273. package/dist/chunk-QQUAB63I.js +63 -0
  274. package/dist/chunk-QQUAB63I.js.map +1 -0
  275. package/dist/chunk-QRNI5JBH.js +18 -0
  276. package/dist/chunk-RHY3HH7P.js +601 -0
  277. package/dist/chunk-RHY3HH7P.js.map +1 -0
  278. package/dist/chunk-RK6F44Y6.js +84 -0
  279. package/dist/chunk-RK6F44Y6.js.map +1 -0
  280. package/dist/chunk-RRF5UOBJ.js +91 -0
  281. package/dist/chunk-RXDLTSWT.js +124 -0
  282. package/dist/chunk-RXDLTSWT.js.map +1 -0
  283. package/dist/chunk-RYED3SPJ.js +42 -0
  284. package/dist/chunk-RYED3SPJ.js.map +1 -0
  285. package/dist/chunk-S7KDBTWT.js +106 -0
  286. package/dist/chunk-S7KDBTWT.js.map +1 -0
  287. package/dist/chunk-SEDEKFYQ.js +1 -0
  288. package/dist/chunk-SOAU2OE2.js +125 -0
  289. package/dist/chunk-SOAU2OE2.js.map +1 -0
  290. package/dist/chunk-TECVW3JP.js +36 -0
  291. package/dist/chunk-TECVW3JP.js.map +1 -0
  292. package/dist/chunk-TFO23QT4.js +88 -0
  293. package/dist/chunk-TFO23QT4.js.map +1 -0
  294. package/dist/chunk-TK4UEOSK.js +76 -0
  295. package/dist/chunk-TK4UEOSK.js.map +1 -0
  296. package/dist/chunk-TKWGAOLV.js +122 -0
  297. package/dist/chunk-TKWGAOLV.js.map +1 -0
  298. package/dist/chunk-TMM4S4IJ.js +597 -0
  299. package/dist/chunk-TMM4S4IJ.js.map +1 -0
  300. package/dist/chunk-TMQLARTH.js +188 -0
  301. package/dist/chunk-TMQLARTH.js.map +1 -0
  302. package/dist/chunk-TPDBFYEG.js +130 -0
  303. package/dist/chunk-TPDBFYEG.js.map +1 -0
  304. package/dist/chunk-TPMQ3G6Z.js +145 -0
  305. package/dist/chunk-TPMQ3G6Z.js.map +1 -0
  306. package/dist/chunk-TZOLIGIG.js +61 -0
  307. package/dist/chunk-TZOLIGIG.js.map +1 -0
  308. package/dist/chunk-U3PN77QT.js +113 -0
  309. package/dist/chunk-U3WSW6PZ.js +277 -0
  310. package/dist/chunk-U4SCL7B7.js +640 -0
  311. package/dist/chunk-U4SCL7B7.js.map +1 -0
  312. package/dist/chunk-UWK5OXUJ.js +156 -0
  313. package/dist/chunk-UWK5OXUJ.js.map +1 -0
  314. package/dist/chunk-UWVJF25J.js +74 -0
  315. package/dist/chunk-UXHQAFNA.js +1317 -0
  316. package/dist/chunk-UXHQAFNA.js.map +1 -0
  317. package/dist/chunk-V5OCT34X.js +1 -0
  318. package/dist/chunk-V5OCT34X.js.map +1 -0
  319. package/dist/chunk-VLXA6PI2.js +304 -0
  320. package/dist/chunk-VLXA6PI2.js.map +1 -0
  321. package/dist/chunk-VNO6ZJ35.js +500 -0
  322. package/dist/chunk-VNO6ZJ35.js.map +1 -0
  323. package/dist/chunk-VW676BEI.js +827 -0
  324. package/dist/chunk-VW676BEI.js.map +1 -0
  325. package/dist/chunk-VWT3F4IV.js +2161 -0
  326. package/dist/chunk-VWT3F4IV.js.map +1 -0
  327. package/dist/chunk-W3LR522O.js +2296 -0
  328. package/dist/chunk-W3LR522O.js.map +1 -0
  329. package/dist/chunk-W4L6CZKA.js +96 -0
  330. package/dist/chunk-W4L6CZKA.js.map +1 -0
  331. package/dist/chunk-W4RVMTHR.js +372 -0
  332. package/dist/chunk-W4RVMTHR.js.map +1 -0
  333. package/dist/chunk-WEHSQBFR.js +188 -0
  334. package/dist/chunk-WEHSQBFR.js.map +1 -0
  335. package/dist/chunk-WELDCG6C.js +380 -0
  336. package/dist/chunk-WELDCG6C.js.map +1 -0
  337. package/dist/chunk-WZYKANL3.js +2800 -0
  338. package/dist/chunk-WZYKANL3.js.map +1 -0
  339. package/dist/chunk-XIG5PDM7.js +48 -0
  340. package/dist/chunk-XJNBEDFE.js +193 -0
  341. package/dist/chunk-XJNBEDFE.js.map +1 -0
  342. package/dist/chunk-XVVIG67A.js +291 -0
  343. package/dist/chunk-XVVIG67A.js.map +1 -0
  344. package/dist/chunk-XVZ7B3HG.js +135 -0
  345. package/dist/chunk-YBPYIAA5.js +73 -0
  346. package/dist/chunk-YBPYIAA5.js.map +1 -0
  347. package/dist/chunk-Z734BLO3.js +21 -0
  348. package/dist/chunk-Z734BLO3.js.map +1 -0
  349. package/dist/chunk-ZKSK55RC.js +269 -0
  350. package/dist/chunk-ZKSK55RC.js.map +1 -0
  351. package/dist/chunk-ZTFCYYEZ.js +69 -0
  352. package/dist/chunk-ZTFCYYEZ.js.map +1 -0
  353. package/dist/chunk-ZY2MNJR6.js +329 -0
  354. package/dist/chunk-ZY2MNJR6.js.map +1 -0
  355. package/dist/cli-D3VpkVwB.d.ts +1136 -0
  356. package/dist/cli.d.ts +42 -10
  357. package/dist/cli.js +121 -58
  358. package/dist/codex-cli-fallback.d.ts +1 -0
  359. package/dist/codex-cli-fallback.js +1 -1
  360. package/dist/commitment-ledger.js +1 -1
  361. package/dist/compat/checks.d.ts +5 -0
  362. package/dist/compat/checks.js +11 -0
  363. package/dist/compat/checks.js.map +1 -0
  364. package/dist/compat/types.d.ts +30 -0
  365. package/dist/compat/types.js +1 -0
  366. package/dist/compat/types.js.map +1 -0
  367. package/dist/compounding/engine.d.ts +221 -0
  368. package/dist/compounding/engine.js +32 -0
  369. package/dist/compounding/engine.js.map +1 -0
  370. package/dist/compounding/preference-consolidator.d.ts +92 -0
  371. package/dist/compounding/preference-consolidator.js +553 -0
  372. package/dist/compounding/preference-consolidator.js.map +1 -0
  373. package/dist/compression-optimizer.d.ts +1 -1
  374. package/dist/config.d.ts +5 -3
  375. package/dist/config.js +9 -4
  376. package/dist/conflict-policy-DyJ2wd-h.d.ts +4 -0
  377. package/dist/connectors/codex-materialize-runner.d.ts +64 -0
  378. package/dist/connectors/codex-materialize-runner.js +33 -0
  379. package/dist/connectors/codex-materialize-runner.js.map +1 -0
  380. package/dist/connectors/codex-materialize.d.ts +195 -0
  381. package/dist/connectors/codex-materialize.js +38 -0
  382. package/dist/connectors/codex-materialize.js.map +1 -0
  383. package/dist/connectors/index.d.ts +444 -0
  384. package/dist/connectors/index.js +115 -0
  385. package/dist/connectors/index.js.map +1 -0
  386. package/dist/connectors-cli-CwbyjGR7.d.ts +257 -0
  387. package/dist/connectors-cli.d.ts +1 -1
  388. package/dist/consolidation-provenance-check.d.ts +4 -2
  389. package/dist/consolidation-undo.d.ts +4 -2
  390. package/dist/contradiction/index.d.ts +258 -0
  391. package/dist/contradiction/index.js +43 -0
  392. package/dist/contradiction/index.js.map +1 -0
  393. package/dist/contradiction-review-ATP4S6IC.js +30 -0
  394. package/dist/contradiction-review-ATP4S6IC.js.map +1 -0
  395. package/dist/contradiction-scan-5A4IDZV5.js +13 -0
  396. package/dist/contradiction-scan-5A4IDZV5.js.map +1 -0
  397. package/dist/conversation-index/backend.d.ts +97 -0
  398. package/dist/conversation-index/backend.js +13 -0
  399. package/dist/conversation-index/backend.js.map +1 -0
  400. package/dist/conversation-index/chunker.d.ts +16 -0
  401. package/dist/conversation-index/chunker.js +8 -0
  402. package/dist/conversation-index/chunker.js.map +1 -0
  403. package/dist/conversation-index/cleanup.d.ts +11 -0
  404. package/dist/conversation-index/cleanup.js +9 -0
  405. package/dist/conversation-index/cleanup.js.map +1 -0
  406. package/dist/conversation-index/faiss-adapter.d.ts +6 -0
  407. package/dist/conversation-index/faiss-adapter.js +16 -0
  408. package/dist/conversation-index/faiss-adapter.js.map +1 -0
  409. package/dist/conversation-index/indexer.d.ts +23 -0
  410. package/dist/conversation-index/indexer.js +15 -0
  411. package/dist/conversation-index/indexer.js.map +1 -0
  412. package/dist/conversation-index/search.d.ts +6 -0
  413. package/dist/conversation-index/search.js +11 -0
  414. package/dist/conversation-index/search.js.map +1 -0
  415. package/dist/day-summary.d.ts +1 -1
  416. package/dist/delinearize.d.ts +1 -1
  417. package/dist/direct-answer-wiring.d.ts +1 -1
  418. package/dist/direct-answer-wiring.js +1 -1
  419. package/dist/direct-answer.d.ts +1 -1
  420. package/dist/embedding-fallback.d.ts +1 -1
  421. package/dist/embedding-fallback.js +2 -2
  422. package/dist/enrichment/index.d.ts +163 -0
  423. package/dist/enrichment/index.js +18 -0
  424. package/dist/enrichment/index.js.map +1 -0
  425. package/dist/entity-retrieval.d.ts +4 -2
  426. package/dist/entity-retrieval.js +9 -6
  427. package/dist/entity-schema.d.ts +1 -1
  428. package/dist/evals.js +1 -1
  429. package/dist/event-order-recall.d.ts +17 -0
  430. package/dist/event-order-recall.js +11 -0
  431. package/dist/event-order-recall.js.map +1 -0
  432. package/dist/evidence-pack.d.ts +3 -1
  433. package/dist/evidence-pack.js +5 -3
  434. package/dist/explicit-capture.d.ts +23 -6
  435. package/dist/explicit-capture.js +2 -2
  436. package/dist/explicit-cue-recall.d.ts +4 -1
  437. package/dist/explicit-cue-recall.js +4 -2
  438. package/dist/extraction-judge-telemetry.d.ts +1 -1
  439. package/dist/extraction-judge-training.d.ts +1 -1
  440. package/dist/extraction-judge-training.js +1 -1
  441. package/dist/extraction-judge.d.ts +1 -1
  442. package/dist/extraction.d.ts +1 -1
  443. package/dist/extraction.js +11 -10
  444. package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
  445. package/dist/fallback-llm.d.ts +4 -1
  446. package/dist/fallback-llm.js +6 -6
  447. package/dist/focused-list-recall.d.ts +17 -0
  448. package/dist/focused-list-recall.js +11 -0
  449. package/dist/focused-list-recall.js.map +1 -0
  450. package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
  451. package/dist/identity-continuity.d.ts +1 -1
  452. package/dist/importance.d.ts +1 -1
  453. package/dist/index-DJ9QWMw-.d.ts +35 -0
  454. package/dist/index.d.ts +107 -715
  455. package/dist/index.js +657 -2611
  456. package/dist/index.js.map +1 -1
  457. package/dist/intent.d.ts +1 -1
  458. package/dist/intent.js +1 -1
  459. package/dist/lcm/archive.d.ts +89 -0
  460. package/dist/lcm/archive.js +12 -0
  461. package/dist/lcm/archive.js.map +1 -0
  462. package/dist/lcm/dag.d.ts +48 -0
  463. package/dist/lcm/dag.js +8 -0
  464. package/dist/lcm/dag.js.map +1 -0
  465. package/dist/lcm/engine.d.ts +116 -0
  466. package/dist/lcm/engine.js +20 -0
  467. package/dist/lcm/engine.js.map +1 -0
  468. package/dist/lcm/index.d.ts +12 -0
  469. package/dist/lcm/index.js +44 -0
  470. package/dist/lcm/index.js.map +1 -0
  471. package/dist/lcm/queue.d.ts +62 -0
  472. package/dist/lcm/queue.js +8 -0
  473. package/dist/lcm/queue.js.map +1 -0
  474. package/dist/lcm/recall.d.ts +20 -0
  475. package/dist/lcm/recall.js +8 -0
  476. package/dist/lcm/recall.js.map +1 -0
  477. package/dist/lcm/schema.d.ts +16 -0
  478. package/dist/lcm/schema.js +14 -0
  479. package/dist/lcm/schema.js.map +1 -0
  480. package/dist/lcm/summarizer.d.ts +38 -0
  481. package/dist/lcm/summarizer.js +12 -0
  482. package/dist/lcm/summarizer.js.map +1 -0
  483. package/dist/lcm/tools.d.ts +29 -0
  484. package/dist/lcm/tools.js +8 -0
  485. package/dist/lcm/tools.js.map +1 -0
  486. package/dist/lifecycle.d.ts +1 -1
  487. package/dist/live-connectors-runner.d.ts +1 -1
  488. package/dist/live-connectors-runner.js +5 -5
  489. package/dist/local-llm.d.ts +8 -4
  490. package/dist/local-llm.js +3 -3
  491. package/dist/maintenance/archive-observations.d.ts +18 -0
  492. package/dist/maintenance/archive-observations.js +8 -0
  493. package/dist/maintenance/archive-observations.js.map +1 -0
  494. package/dist/maintenance/backup-stamp.d.ts +3 -0
  495. package/dist/maintenance/backup-stamp.js +8 -0
  496. package/dist/maintenance/backup-stamp.js.map +1 -0
  497. package/dist/maintenance/memory-governance-cron.d.ts +85 -0
  498. package/dist/maintenance/memory-governance-cron.js +22 -0
  499. package/dist/maintenance/memory-governance-cron.js.map +1 -0
  500. package/dist/maintenance/memory-governance.d.ts +137 -0
  501. package/dist/maintenance/memory-governance.js +40 -0
  502. package/dist/maintenance/memory-governance.js.map +1 -0
  503. package/dist/maintenance/migrate-observations.d.ts +18 -0
  504. package/dist/maintenance/migrate-observations.js +9 -0
  505. package/dist/maintenance/migrate-observations.js.map +1 -0
  506. package/dist/maintenance/observation-ledger-utils.d.ts +10 -0
  507. package/dist/maintenance/observation-ledger-utils.js +10 -0
  508. package/dist/maintenance/observation-ledger-utils.js.map +1 -0
  509. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +15 -0
  510. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +28 -0
  511. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js.map +1 -0
  512. package/dist/maintenance/rebuild-memory-projection.d.ts +77 -0
  513. package/dist/maintenance/rebuild-memory-projection.js +35 -0
  514. package/dist/maintenance/rebuild-memory-projection.js.map +1 -0
  515. package/dist/maintenance/rebuild-observations.d.ts +17 -0
  516. package/dist/maintenance/rebuild-observations.js +9 -0
  517. package/dist/maintenance/rebuild-observations.js.map +1 -0
  518. package/dist/mcp-memory-inspector-app.d.ts +124 -0
  519. package/dist/mcp-memory-inspector-app.js +20 -0
  520. package/dist/mcp-memory-inspector-app.js.map +1 -0
  521. package/dist/memory-action-policy.d.ts +1 -1
  522. package/dist/memory-cache.d.ts +1 -1
  523. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  524. package/dist/memory-projection-store.d.ts +108 -3
  525. package/dist/memory-projection-store.js +2 -1
  526. package/dist/memory-provenance.d.ts +57 -0
  527. package/dist/memory-provenance.js +13 -0
  528. package/dist/memory-provenance.js.map +1 -0
  529. package/dist/memory-worth-outcomes.d.ts +4 -2
  530. package/dist/migrate/from-engram.d.ts +24 -0
  531. package/dist/migrate/from-engram.js +12 -0
  532. package/dist/migrate/from-engram.js.map +1 -0
  533. package/dist/models-json.d.ts +1 -1
  534. package/dist/namespaces/migrate.d.ts +50 -0
  535. package/dist/namespaces/migrate.js +50 -0
  536. package/dist/namespaces/migrate.js.map +1 -0
  537. package/dist/namespaces/principal.d.ts +17 -0
  538. package/dist/namespaces/principal.js +16 -0
  539. package/dist/namespaces/principal.js.map +1 -0
  540. package/dist/namespaces/search.d.ts +46 -0
  541. package/dist/namespaces/search.js +28 -0
  542. package/dist/namespaces/search.js.map +1 -0
  543. package/dist/namespaces/storage.d.ts +32 -0
  544. package/dist/namespaces/storage.js +28 -0
  545. package/dist/namespaces/storage.js.map +1 -0
  546. package/dist/native-knowledge.d.ts +1 -1
  547. package/dist/network/tailscale.d.ts +41 -0
  548. package/dist/network/tailscale.js +9 -0
  549. package/dist/network/tailscale.js.map +1 -0
  550. package/dist/network/webdav.d.ts +39 -0
  551. package/dist/network/webdav.js +10 -0
  552. package/dist/network/webdav.js.map +1 -0
  553. package/dist/objective-state-writers.d.ts +1 -1
  554. package/dist/objective-state-writers.js +2 -2
  555. package/dist/operator-toolkit.d.ts +4 -2
  556. package/dist/operator-toolkit.js +35 -17
  557. package/dist/opik-exporter.js +2 -2
  558. package/dist/opik-exporter.js.map +1 -1
  559. package/dist/orchestrator-DuWl9Hwx.d.ts +1244 -0
  560. package/dist/orchestrator.d.ts +24 -7
  561. package/dist/orchestrator.js +107 -65
  562. package/dist/path-MR5JPYOP.js +9 -0
  563. package/dist/path-MR5JPYOP.js.map +1 -0
  564. package/dist/patterns-cli.d.ts +1 -1
  565. package/dist/policy-runtime.d.ts +1 -1
  566. package/dist/qmd-recall-cache.d.ts +2 -2
  567. package/dist/qmd.d.ts +103 -4
  568. package/dist/qmd.js +23 -5
  569. package/dist/recall-disclosure-escalation.d.ts +1 -1
  570. package/dist/recall-explain-renderer.d.ts +3 -1
  571. package/dist/recall-explain-renderer.js +5 -3
  572. package/dist/recall-state.d.ts +1 -1
  573. package/dist/recall-tag-filter.d.ts +3 -1
  574. package/dist/recall-xray-cli.d.ts +3 -1
  575. package/dist/recall-xray-cli.js +6 -4
  576. package/dist/recall-xray-renderer.d.ts +3 -1
  577. package/dist/recall-xray-renderer.js +5 -3
  578. package/dist/recall-xray.d.ts +8 -1
  579. package/dist/recall-xray.js +4 -2
  580. package/dist/replay/normalizers/chatgpt.d.ts +6 -0
  581. package/dist/replay/normalizers/chatgpt.js +11 -0
  582. package/dist/replay/normalizers/chatgpt.js.map +1 -0
  583. package/dist/replay/normalizers/claude.d.ts +6 -0
  584. package/dist/replay/normalizers/claude.js +11 -0
  585. package/dist/replay/normalizers/claude.js.map +1 -0
  586. package/dist/replay/normalizers/openclaw.d.ts +6 -0
  587. package/dist/replay/normalizers/openclaw.js +11 -0
  588. package/dist/replay/normalizers/openclaw.js.map +1 -0
  589. package/dist/replay/normalizers/shared.d.ts +16 -0
  590. package/dist/replay/normalizers/shared.js +14 -0
  591. package/dist/replay/normalizers/shared.js.map +1 -0
  592. package/dist/replay/runner.d.ts +35 -0
  593. package/dist/replay/runner.js +16 -0
  594. package/dist/replay/runner.js.map +1 -0
  595. package/dist/replay/types.d.ts +57 -0
  596. package/dist/replay/types.js +19 -0
  597. package/dist/replay/types.js.map +1 -0
  598. package/dist/resolution-B7FNQSSP.js +12 -0
  599. package/dist/resolution-B7FNQSSP.js.map +1 -0
  600. package/dist/resolve-auth-token.d.ts +1 -1
  601. package/dist/resolve-provider-secret.js +2 -2
  602. package/dist/response-guidance-recall.d.ts +18 -0
  603. package/dist/response-guidance-recall.js +11 -0
  604. package/dist/response-guidance-recall.js.map +1 -0
  605. package/dist/resume-bundles.js +7 -5
  606. package/dist/retrieval-agents.d.ts +2 -2
  607. package/dist/retrieval-tiers.d.ts +1 -1
  608. package/dist/routing/engine.d.ts +35 -0
  609. package/dist/routing/engine.js +16 -0
  610. package/dist/routing/engine.js.map +1 -0
  611. package/dist/routing/store.d.ts +27 -0
  612. package/dist/routing/store.js +10 -0
  613. package/dist/routing/store.js.map +1 -0
  614. package/dist/runtime/better-sqlite.d.ts +8 -0
  615. package/dist/runtime/better-sqlite.js +10 -0
  616. package/dist/runtime/better-sqlite.js.map +1 -0
  617. package/dist/runtime/child-process.d.ts +32 -0
  618. package/dist/runtime/child-process.js +10 -0
  619. package/dist/runtime/child-process.js.map +1 -0
  620. package/dist/runtime/env.d.ts +5 -0
  621. package/dist/runtime/env.js +12 -0
  622. package/dist/runtime/env.js.map +1 -0
  623. package/dist/sdk-compat.js +1 -1
  624. package/dist/search/document-scanner.d.ts +22 -0
  625. package/dist/search/document-scanner.js +8 -0
  626. package/dist/search/document-scanner.js.map +1 -0
  627. package/dist/search/embed-helper.d.ts +35 -0
  628. package/dist/search/embed-helper.js +9 -0
  629. package/dist/search/embed-helper.js.map +1 -0
  630. package/dist/search/factory.d.ts +32 -0
  631. package/dist/search/factory.js +29 -0
  632. package/dist/search/factory.js.map +1 -0
  633. package/dist/search/index.d.ts +15 -0
  634. package/dist/search/index.js +50 -0
  635. package/dist/search/index.js.map +1 -0
  636. package/dist/search/lancedb-backend.d.ts +51 -0
  637. package/dist/search/lancedb-backend.js +10 -0
  638. package/dist/search/lancedb-backend.js.map +1 -0
  639. package/dist/search/meilisearch-backend.d.ts +48 -0
  640. package/dist/search/meilisearch-backend.js +10 -0
  641. package/dist/search/meilisearch-backend.js.map +1 -0
  642. package/dist/search/noop-backend.d.ts +26 -0
  643. package/dist/search/noop-backend.js +8 -0
  644. package/dist/search/noop-backend.js.map +1 -0
  645. package/dist/search/orama-backend.d.ts +53 -0
  646. package/dist/search/orama-backend.js +10 -0
  647. package/dist/search/orama-backend.js.map +1 -0
  648. package/dist/search/port.d.ts +61 -0
  649. package/dist/search/port.js +1 -0
  650. package/dist/search/port.js.map +1 -0
  651. package/dist/search/remote-backend.d.ts +39 -0
  652. package/dist/search/remote-backend.js +9 -0
  653. package/dist/search/remote-backend.js.map +1 -0
  654. package/dist/secure-store/index.d.ts +890 -0
  655. package/dist/secure-store/index.js +156 -0
  656. package/dist/secure-store/index.js.map +1 -0
  657. package/dist/semantic-VwGI14Ok.d.ts +69 -0
  658. package/dist/semantic-consolidation-4HkHWgeI.d.ts +180 -0
  659. package/dist/semantic-consolidation.d.ts +3 -3
  660. package/dist/semantic-consolidation.js +15 -8
  661. package/dist/semantic-rule-promotion.js +9 -6
  662. package/dist/semantic-rule-verifier.d.ts +1 -1
  663. package/dist/semantic-rule-verifier.js +10 -7
  664. package/dist/session-observer-bands.d.ts +1 -1
  665. package/dist/session-observer-state.d.ts +1 -1
  666. package/dist/shared-context/manager.d.ts +131 -0
  667. package/dist/shared-context/manager.js +15 -0
  668. package/dist/shared-context/manager.js.map +1 -0
  669. package/dist/signal.d.ts +1 -1
  670. package/dist/skills-registry.js +13 -1
  671. package/dist/skills-registry.js.map +1 -1
  672. package/dist/state-store-VZU2IA53.js +16 -0
  673. package/dist/state-store-VZU2IA53.js.map +1 -0
  674. package/dist/storage-paths.d.ts +9 -0
  675. package/dist/storage-paths.js +20 -0
  676. package/dist/storage-paths.js.map +1 -0
  677. package/dist/storage.d.ts +6 -2
  678. package/dist/storage.js +8 -5
  679. package/dist/summarizer.d.ts +6 -1
  680. package/dist/summarizer.js +11 -10
  681. package/dist/summary-snapshot.d.ts +1 -1
  682. package/dist/summary-snapshot.js +2 -1
  683. package/dist/surfaces/dreams.d.ts +16 -0
  684. package/dist/surfaces/dreams.js +282 -0
  685. package/dist/surfaces/dreams.js.map +1 -0
  686. package/dist/surfaces/heartbeat.d.ts +17 -0
  687. package/dist/surfaces/heartbeat.js +265 -0
  688. package/dist/surfaces/heartbeat.js.map +1 -0
  689. package/dist/targeted-fact-recall.d.ts +17 -0
  690. package/dist/targeted-fact-recall.js +11 -0
  691. package/dist/targeted-fact-recall.js.map +1 -0
  692. package/dist/telemetry-transcript.d.ts +7 -0
  693. package/dist/telemetry-transcript.js +16 -0
  694. package/dist/telemetry-transcript.js.map +1 -0
  695. package/dist/temporal-supersession.d.ts +4 -2
  696. package/dist/temporal-supersession.js +2 -1
  697. package/dist/temporal-validity.d.ts +1 -1
  698. package/dist/threading.d.ts +6 -1
  699. package/dist/threading.js +2 -1
  700. package/dist/tier-migration.d.ts +5 -3
  701. package/dist/tier-routing.d.ts +1 -1
  702. package/dist/tokens.js +2 -2
  703. package/dist/topics.d.ts +1 -1
  704. package/dist/transcript.d.ts +16 -2
  705. package/dist/transcript.js +2 -1
  706. package/dist/transfer/autodetect.d.ts +4 -0
  707. package/dist/transfer/autodetect.js +15 -0
  708. package/dist/transfer/autodetect.js.map +1 -0
  709. package/dist/transfer/backup.d.ts +21 -0
  710. package/dist/transfer/backup.js +17 -0
  711. package/dist/transfer/backup.js.map +1 -0
  712. package/dist/transfer/capsule-export.d.ts +113 -0
  713. package/dist/transfer/capsule-export.js +19 -0
  714. package/dist/transfer/capsule-export.js.map +1 -0
  715. package/dist/transfer/capsule-import.d.ts +124 -0
  716. package/dist/transfer/capsule-import.js +16 -0
  717. package/dist/transfer/capsule-import.js.map +1 -0
  718. package/dist/transfer/constants.d.ts +13 -0
  719. package/dist/transfer/constants.js +12 -0
  720. package/dist/transfer/constants.js.map +1 -0
  721. package/dist/transfer/export-json.d.ts +11 -0
  722. package/dist/transfer/export-json.js +11 -0
  723. package/dist/transfer/export-json.js.map +1 -0
  724. package/dist/transfer/export-md.d.ts +10 -0
  725. package/dist/transfer/export-md.js +13 -0
  726. package/dist/transfer/export-md.js.map +1 -0
  727. package/dist/transfer/export-sqlite.d.ts +9 -0
  728. package/dist/transfer/export-sqlite.js +12 -0
  729. package/dist/transfer/export-sqlite.js.map +1 -0
  730. package/dist/transfer/fs-utils.d.ts +61 -0
  731. package/dist/transfer/fs-utils.js +40 -0
  732. package/dist/transfer/fs-utils.js.map +1 -0
  733. package/dist/transfer/import-json.d.ts +16 -0
  734. package/dist/transfer/import-json.js +13 -0
  735. package/dist/transfer/import-json.js.map +1 -0
  736. package/dist/transfer/import-md.d.ts +14 -0
  737. package/dist/transfer/import-md.js +11 -0
  738. package/dist/transfer/import-md.js.map +1 -0
  739. package/dist/transfer/import-sqlite.d.ts +14 -0
  740. package/dist/transfer/import-sqlite.js +12 -0
  741. package/dist/transfer/import-sqlite.js.map +1 -0
  742. package/dist/transfer/sqlite-schema.d.ts +4 -0
  743. package/dist/transfer/sqlite-schema.js +10 -0
  744. package/dist/transfer/sqlite-schema.js.map +1 -0
  745. package/dist/transfer/types.d.ts +916 -0
  746. package/dist/transfer/types.js +30 -0
  747. package/dist/transfer/types.js.map +1 -0
  748. package/dist/trust-zones.d.ts +3 -2
  749. package/dist/trust-zones.js +1 -1
  750. package/dist/types.d.ts +88 -3
  751. package/dist/types.js +1 -1
  752. package/dist/user-model.d.ts +37 -0
  753. package/dist/user-model.js +28 -0
  754. package/dist/user-model.js.map +1 -0
  755. package/dist/utility-runtime.d.ts +1 -1
  756. package/dist/verified-recall.js +11 -8
  757. package/dist/work/board.d.ts +43 -0
  758. package/dist/work/board.js +14 -0
  759. package/dist/work/board.js.map +1 -0
  760. package/dist/work/boundary.d.ts +8 -0
  761. package/dist/work/boundary.js +14 -0
  762. package/dist/work/boundary.js.map +1 -0
  763. package/dist/work/storage.d.ts +39 -0
  764. package/dist/work/storage.js +11 -0
  765. package/dist/work/storage.js.map +1 -0
  766. package/dist/work/types.d.ts +75 -0
  767. package/dist/work/types.js +1 -0
  768. package/dist/work/types.js.map +1 -0
  769. package/package.json +2767 -6
  770. package/scripts/faiss_index.py +816 -0
  771. package/scripts/faiss_requirements.txt +3 -0
  772. package/skills/remnic-entities/SKILL.md +51 -0
  773. package/skills/remnic-memory-workflow/SKILL.md +61 -0
  774. package/skills/remnic-recall/SKILL.md +51 -0
  775. package/skills/remnic-remember/SKILL.md +56 -0
  776. package/skills/remnic-search/SKILL.md +51 -0
  777. package/skills/remnic-status/SKILL.md +51 -0
  778. package/src/abort-error.test.ts +49 -0
  779. package/src/abort-error.ts +46 -0
  780. package/src/abstraction-nodes.ts +162 -0
  781. package/src/access-audit.test.ts +178 -0
  782. package/src/access-audit.ts +125 -0
  783. package/src/access-cli.test.ts +439 -0
  784. package/src/access-cli.ts +438 -0
  785. package/src/access-http.test.ts +225 -0
  786. package/src/access-http.ts +1899 -0
  787. package/src/access-idempotency.ts +232 -0
  788. package/src/access-mcp.test.ts +568 -0
  789. package/src/access-mcp.ts +3056 -0
  790. package/src/access-schema-pi.test.ts +60 -0
  791. package/src/access-schema.ts +522 -0
  792. package/src/access-service-namespace.test.ts +123 -0
  793. package/src/access-service.ts +5629 -0
  794. package/src/action-confidence.test.ts +206 -0
  795. package/src/action-confidence.ts +466 -0
  796. package/src/active-memory-bridge.test.ts +285 -0
  797. package/src/active-memory-bridge.ts +217 -0
  798. package/src/active-recall.test.ts +484 -0
  799. package/src/active-recall.ts +459 -0
  800. package/src/adapters/claude-code.ts +56 -0
  801. package/src/adapters/codex.ts +57 -0
  802. package/src/adapters/hermes.ts +64 -0
  803. package/src/adapters/index.ts +6 -0
  804. package/src/adapters/registry.ts +41 -0
  805. package/src/adapters/replit.ts +55 -0
  806. package/src/adapters/types.ts +51 -0
  807. package/src/behavior-learner.ts +144 -0
  808. package/src/behavior-signals.ts +73 -0
  809. package/src/binary-lifecycle/backend.ts +117 -0
  810. package/src/binary-lifecycle/index.ts +35 -0
  811. package/src/binary-lifecycle/manifest.ts +79 -0
  812. package/src/binary-lifecycle/pipeline.ts +352 -0
  813. package/src/binary-lifecycle/scanner.ts +89 -0
  814. package/src/binary-lifecycle/types.ts +89 -0
  815. package/src/bootstrap.ts +178 -0
  816. package/src/boxes.ts +521 -0
  817. package/src/briefing.test.ts +1535 -0
  818. package/src/briefing.ts +1382 -0
  819. package/src/buffer-session.test.ts +443 -0
  820. package/src/buffer-surprise-report.ts +176 -0
  821. package/src/buffer-surprise-telemetry.test.ts +606 -0
  822. package/src/buffer-surprise-trigger.test.ts +766 -0
  823. package/src/buffer-surprise.test.ts +339 -0
  824. package/src/buffer-surprise.ts +203 -0
  825. package/src/buffer.ts +900 -0
  826. package/src/bulk-import/cli-command.test.ts +204 -0
  827. package/src/bulk-import/index.ts +34 -0
  828. package/src/bulk-import/pipeline.test.ts +445 -0
  829. package/src/bulk-import/pipeline.ts +178 -0
  830. package/src/bulk-import/registry.test.ts +151 -0
  831. package/src/bulk-import/registry.ts +72 -0
  832. package/src/bulk-import/types.test.ts +272 -0
  833. package/src/bulk-import/types.ts +145 -0
  834. package/src/calibration.ts +394 -0
  835. package/src/capsule-cli.test.ts +398 -0
  836. package/src/capsule-cli.ts +565 -0
  837. package/src/causal-behavior.ts +308 -0
  838. package/src/causal-chain.ts +419 -0
  839. package/src/causal-consolidation.ts +370 -0
  840. package/src/causal-retrieval.ts +286 -0
  841. package/src/causal-trajectory-graph.ts +60 -0
  842. package/src/causal-trajectory.ts +303 -0
  843. package/src/chunking.ts +220 -0
  844. package/src/citations.ts +232 -0
  845. package/src/cli.ts +9403 -0
  846. package/src/codex-cli-fallback.ts +162 -0
  847. package/src/codex-thread-key.ts +1 -0
  848. package/src/coding/access-coding-context.test.ts +197 -0
  849. package/src/coding/coding-branch-scope.test.ts +281 -0
  850. package/src/coding/coding-namespace.test.ts +360 -0
  851. package/src/coding/coding-namespace.ts +412 -0
  852. package/src/coding/coding-orchestrator.test.ts +249 -0
  853. package/src/coding/git-context.test.ts +507 -0
  854. package/src/coding/git-context.ts +336 -0
  855. package/src/coding/mcp-set-coding-context.test.ts +174 -0
  856. package/src/coding/review-context.test.ts +316 -0
  857. package/src/coding/review-context.ts +349 -0
  858. package/src/coding/wire-coding-context.test.ts +468 -0
  859. package/src/commitment-ledger.test.ts +78 -0
  860. package/src/commitment-ledger.ts +337 -0
  861. package/src/compat/checks.test.ts +206 -0
  862. package/src/compat/checks.ts +716 -0
  863. package/src/compat/types.ts +33 -0
  864. package/src/compounding/engine.ts +1686 -0
  865. package/src/compounding/preference-consolidator.ts +778 -0
  866. package/src/compression-optimizer.ts +312 -0
  867. package/src/config.test.ts +930 -0
  868. package/src/config.ts +3807 -0
  869. package/src/connectors/codex/instructions.md +160 -0
  870. package/src/connectors/codex/resources/namespace-cheatsheet.md +48 -0
  871. package/src/connectors/codex-marketplace.ts +500 -0
  872. package/src/connectors/codex-materialize-runner.ts +212 -0
  873. package/src/connectors/codex-materialize.ts +983 -0
  874. package/src/connectors/coerce.ts +62 -0
  875. package/src/connectors/index.test.ts +1570 -0
  876. package/src/connectors/index.ts +3222 -0
  877. package/src/connectors/live/framework.ts +164 -0
  878. package/src/connectors/live/github.test.ts +1218 -0
  879. package/src/connectors/live/github.ts +1068 -0
  880. package/src/connectors/live/gmail.test.ts +1706 -0
  881. package/src/connectors/live/gmail.ts +1293 -0
  882. package/src/connectors/live/google-drive.test.ts +696 -0
  883. package/src/connectors/live/google-drive.ts +724 -0
  884. package/src/connectors/live/index.ts +101 -0
  885. package/src/connectors/live/live-connectors.test.ts +689 -0
  886. package/src/connectors/live/notion.test.ts +1109 -0
  887. package/src/connectors/live/notion.ts +978 -0
  888. package/src/connectors/live/registry.ts +103 -0
  889. package/src/connectors/live/state-store.ts +399 -0
  890. package/src/connectors/live/transient-errors.ts +150 -0
  891. package/src/connectors/weclone-installer.test.ts +850 -0
  892. package/src/connectors-cli.ts +513 -0
  893. package/src/console/state.test.ts +224 -0
  894. package/src/console/state.ts +514 -0
  895. package/src/console/trace.test.ts +813 -0
  896. package/src/console/trace.ts +603 -0
  897. package/src/console/tui.test.ts +582 -0
  898. package/src/console/tui.ts +508 -0
  899. package/src/consolidation-operator.ts +182 -0
  900. package/src/consolidation-provenance-check.ts +551 -0
  901. package/src/consolidation-undo.ts +718 -0
  902. package/src/contradiction/contradiction-judge.test.ts +189 -0
  903. package/src/contradiction/contradiction-judge.ts +333 -0
  904. package/src/contradiction/contradiction-review.ts +574 -0
  905. package/src/contradiction/contradiction-scan.ts +504 -0
  906. package/src/contradiction/contradiction.test.ts +2230 -0
  907. package/src/contradiction/index.ts +37 -0
  908. package/src/contradiction/resolution.ts +383 -0
  909. package/src/conversation-index/backend.ts +323 -0
  910. package/src/conversation-index/chunker.ts +47 -0
  911. package/src/conversation-index/cleanup.ts +53 -0
  912. package/src/conversation-index/faiss-adapter.ts +384 -0
  913. package/src/conversation-index/indexer.test.ts +164 -0
  914. package/src/conversation-index/indexer.ts +192 -0
  915. package/src/conversation-index/search.ts +37 -0
  916. package/src/cross-namespace-budget.test.ts +275 -0
  917. package/src/cross-namespace-budget.ts +365 -0
  918. package/src/cue-anchors.ts +163 -0
  919. package/src/curation/index.ts +544 -0
  920. package/src/dashboard-runtime.ts +337 -0
  921. package/src/day-summary.ts +122 -0
  922. package/src/dedup/index.ts +330 -0
  923. package/src/dedup/semantic.test.ts +1577 -0
  924. package/src/dedup/semantic.ts +148 -0
  925. package/src/delinearize.ts +193 -0
  926. package/src/direct-answer-wiring.test.ts +473 -0
  927. package/src/direct-answer-wiring.ts +180 -0
  928. package/src/direct-answer.test.ts +484 -0
  929. package/src/direct-answer.ts +273 -0
  930. package/src/embedding-fallback.ts +565 -0
  931. package/src/enrichment/audit.ts +89 -0
  932. package/src/enrichment/index.ts +27 -0
  933. package/src/enrichment/pipeline.ts +197 -0
  934. package/src/enrichment/provider-registry.ts +85 -0
  935. package/src/enrichment/types.ts +100 -0
  936. package/src/enrichment/web-search-provider.ts +63 -0
  937. package/src/entity-retrieval.ts +774 -0
  938. package/src/entity-schema.ts +239 -0
  939. package/src/evals.ts +1312 -0
  940. package/src/event-order-recall.test.ts +4164 -0
  941. package/src/event-order-recall.ts +2802 -0
  942. package/src/evidence-pack.test.ts +89 -0
  943. package/src/evidence-pack.ts +388 -0
  944. package/src/explicit-capture.ts +530 -0
  945. package/src/explicit-cue-recall.test.ts +3019 -0
  946. package/src/explicit-cue-recall.ts +5545 -0
  947. package/src/extraction-judge-telemetry.ts +234 -0
  948. package/src/extraction-judge-training.ts +221 -0
  949. package/src/extraction-judge.ts +846 -0
  950. package/src/extraction-timeout.test.ts +265 -0
  951. package/src/extraction.ts +2719 -0
  952. package/src/fallback-llm.test.ts +1060 -0
  953. package/src/fallback-llm.ts +918 -0
  954. package/src/focused-list-recall.test.ts +734 -0
  955. package/src/focused-list-recall.ts +1160 -0
  956. package/src/graph-dashboard-diff.ts +35 -0
  957. package/src/graph-dashboard-key.ts +5 -0
  958. package/src/graph-dashboard-parser.ts +104 -0
  959. package/src/graph-edge-reinforcement.ts +192 -0
  960. package/src/graph-events.ts +151 -0
  961. package/src/graph-recall.test.ts +164 -0
  962. package/src/graph-recall.ts +189 -0
  963. package/src/graph-retrieval.test.ts +809 -0
  964. package/src/graph-retrieval.ts +823 -0
  965. package/src/graph-snapshot.ts +329 -0
  966. package/src/graph.ts +813 -0
  967. package/src/harmonic-retrieval.ts +223 -0
  968. package/src/himem.ts +154 -0
  969. package/src/hygiene.ts +87 -0
  970. package/src/identity-continuity.ts +333 -0
  971. package/src/importance.ts +328 -0
  972. package/src/importers/base.test.ts +294 -0
  973. package/src/importers/base.ts +436 -0
  974. package/src/importers/index.ts +21 -0
  975. package/src/index.ts +1204 -0
  976. package/src/intent.ts +154 -0
  977. package/src/json-extract.ts +85 -0
  978. package/src/json-store.ts +42 -0
  979. package/src/lcm/archive.ts +617 -0
  980. package/src/lcm/dag.ts +199 -0
  981. package/src/lcm/engine.ts +645 -0
  982. package/src/lcm/index.ts +7 -0
  983. package/src/lcm/queue.test.ts +178 -0
  984. package/src/lcm/queue.ts +200 -0
  985. package/src/lcm/recall.ts +117 -0
  986. package/src/lcm/schema.ts +154 -0
  987. package/src/lcm/summarizer.ts +235 -0
  988. package/src/lcm/tools.ts +191 -0
  989. package/src/lcm-engine.test.ts +660 -0
  990. package/src/legacy-hook-compat.test.ts +20 -0
  991. package/src/legacy-hook-compat.ts +45 -0
  992. package/src/lifecycle.ts +289 -0
  993. package/src/live-connectors-runner.ts +385 -0
  994. package/src/local-llm-qos.test.ts +303 -0
  995. package/src/local-llm-thinking.test.ts +292 -0
  996. package/src/local-llm.ts +1464 -0
  997. package/src/logger.ts +49 -0
  998. package/src/maintenance/archive-observations.ts +147 -0
  999. package/src/maintenance/backup-stamp.ts +3 -0
  1000. package/src/maintenance/dreams-ledger.ts +516 -0
  1001. package/src/maintenance/first-start-migration.ts +362 -0
  1002. package/src/maintenance/forget.test.ts +206 -0
  1003. package/src/maintenance/forget.ts +126 -0
  1004. package/src/maintenance/graph-edge-decay.test.ts +409 -0
  1005. package/src/maintenance/graph-edge-decay.ts +394 -0
  1006. package/src/maintenance/memory-governance-cron.ts +447 -0
  1007. package/src/maintenance/memory-governance.ts +1039 -0
  1008. package/src/maintenance/migrate-observations.ts +216 -0
  1009. package/src/maintenance/observation-ledger-utils.ts +54 -0
  1010. package/src/maintenance/pattern-reinforcement.test.ts +875 -0
  1011. package/src/maintenance/pattern-reinforcement.ts +369 -0
  1012. package/src/maintenance/purge.ts +334 -0
  1013. package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +78 -0
  1014. package/src/maintenance/rebuild-memory-projection.ts +1234 -0
  1015. package/src/maintenance/rebuild-observations.ts +178 -0
  1016. package/src/maintenance/tier-stats.test.ts +378 -0
  1017. package/src/maintenance/tier-stats.ts +222 -0
  1018. package/src/mcp-memory-inspector-app.ts +421 -0
  1019. package/src/memory-action-policy.ts +80 -0
  1020. package/src/memory-cache.ts +208 -0
  1021. package/src/memory-extension/claude-code-publisher.ts +51 -0
  1022. package/src/memory-extension/codex-publisher.ts +149 -0
  1023. package/src/memory-extension/hermes-publisher.ts +51 -0
  1024. package/src/memory-extension/index.ts +100 -0
  1025. package/src/memory-extension/shared-instructions.ts +133 -0
  1026. package/src/memory-extension/types.ts +86 -0
  1027. package/src/memory-extension-host/host-discovery.ts +276 -0
  1028. package/src/memory-extension-host/index.ts +14 -0
  1029. package/src/memory-extension-host/render-extensions-block.ts +73 -0
  1030. package/src/memory-extension-host/types.ts +21 -0
  1031. package/src/memory-lifecycle-ledger-utils.ts +116 -0
  1032. package/src/memory-projection-format.ts +11 -0
  1033. package/src/memory-projection-store.ts +951 -0
  1034. package/src/memory-provenance.test.ts +196 -0
  1035. package/src/memory-provenance.ts +484 -0
  1036. package/src/memory-worth-bench.test.ts +71 -0
  1037. package/src/memory-worth-bench.ts +265 -0
  1038. package/src/memory-worth-filter.test.ts +209 -0
  1039. package/src/memory-worth-filter.ts +204 -0
  1040. package/src/memory-worth-frontmatter.test.ts +311 -0
  1041. package/src/memory-worth-outcomes.test.ts +316 -0
  1042. package/src/memory-worth-outcomes.ts +286 -0
  1043. package/src/memory-worth.test.ts +317 -0
  1044. package/src/memory-worth.ts +215 -0
  1045. package/src/message-parts/index.ts +806 -0
  1046. package/src/message-parts/message-parts.test.ts +421 -0
  1047. package/src/migrate/from-engram.ts +789 -0
  1048. package/src/model-registry.ts +313 -0
  1049. package/src/models-json.ts +76 -0
  1050. package/src/namespaces/migrate.ts +187 -0
  1051. package/src/namespaces/path.ts +25 -0
  1052. package/src/namespaces/principal.test.ts +195 -0
  1053. package/src/namespaces/principal.ts +86 -0
  1054. package/src/namespaces/search.test.ts +105 -0
  1055. package/src/namespaces/search.ts +233 -0
  1056. package/src/namespaces/storage.ts +74 -0
  1057. package/src/native-knowledge.ts +1823 -0
  1058. package/src/negative.ts +72 -0
  1059. package/src/network/tailscale.ts +179 -0
  1060. package/src/network/webdav.ts +385 -0
  1061. package/src/objective-state-writers.ts +951 -0
  1062. package/src/objective-state.ts +320 -0
  1063. package/src/onboarding/index.ts +529 -0
  1064. package/src/openai-chat-compat.ts +56 -0
  1065. package/src/operator-toolkit.ts +2132 -0
  1066. package/src/opik-exporter.test.ts +72 -0
  1067. package/src/opik-exporter.ts +587 -0
  1068. package/src/orchestrator-extraction-queue.test.ts +197 -0
  1069. package/src/orchestrator-flush.test.ts +1171 -0
  1070. package/src/orchestrator-pattern-reinforcement.test.ts +128 -0
  1071. package/src/orchestrator-source-attribution.test.ts +701 -0
  1072. package/src/orchestrator.ts +16368 -0
  1073. package/src/page-versioning.ts +450 -0
  1074. package/src/patterns-cli.ts +574 -0
  1075. package/src/peers/index.ts +54 -0
  1076. package/src/peers/migrate-from-identity-anchor.test.ts +291 -0
  1077. package/src/peers/migrate-from-identity-anchor.ts +350 -0
  1078. package/src/peers/peers.test.ts +419 -0
  1079. package/src/peers/profile-reasoner.ts +694 -0
  1080. package/src/peers/storage.ts +1350 -0
  1081. package/src/peers/types.ts +138 -0
  1082. package/src/plugin-id.ts +84 -0
  1083. package/src/policy-runtime.ts +209 -0
  1084. package/src/procedural/procedure-miner.ts +150 -0
  1085. package/src/procedural/procedure-recall.ts +93 -0
  1086. package/src/procedural/procedure-stats.ts +213 -0
  1087. package/src/procedural/procedure-types.ts +132 -0
  1088. package/src/procedural/reinforcement-core.test.ts +132 -0
  1089. package/src/procedural/reinforcement-core.ts +73 -0
  1090. package/src/profiling.test.ts +263 -0
  1091. package/src/profiling.ts +435 -0
  1092. package/src/projection/index.ts +398 -0
  1093. package/src/qmd-recall-cache.test.ts +138 -0
  1094. package/src/qmd-recall-cache.ts +111 -0
  1095. package/src/qmd.test.ts +257 -0
  1096. package/src/qmd.ts +2614 -0
  1097. package/src/reasoning-trace-recall.ts +201 -0
  1098. package/src/reasoning-trace-types.ts +235 -0
  1099. package/src/recall-audit-anomaly.test.ts +246 -0
  1100. package/src/recall-audit-anomaly.ts +297 -0
  1101. package/src/recall-audit.test.ts +51 -0
  1102. package/src/recall-audit.ts +72 -0
  1103. package/src/recall-budget-config.test.ts +87 -0
  1104. package/src/recall-disclosure-escalation.test.ts +196 -0
  1105. package/src/recall-disclosure-escalation.ts +158 -0
  1106. package/src/recall-disclosure-shaping.test.ts +146 -0
  1107. package/src/recall-disclosure.test.ts +214 -0
  1108. package/src/recall-explain-renderer.test.ts +140 -0
  1109. package/src/recall-explain-renderer.ts +356 -0
  1110. package/src/recall-mmr.test.ts +808 -0
  1111. package/src/recall-mmr.ts +607 -0
  1112. package/src/recall-qos.test.ts +85 -0
  1113. package/src/recall-qos.ts +82 -0
  1114. package/src/recall-query-policy.ts +221 -0
  1115. package/src/recall-state.test.ts +233 -0
  1116. package/src/recall-state.ts +456 -0
  1117. package/src/recall-tag-filter.ts +143 -0
  1118. package/src/recall-tokenization.ts +35 -0
  1119. package/src/recall-xray-cli.test.ts +118 -0
  1120. package/src/recall-xray-cli.ts +100 -0
  1121. package/src/recall-xray-disclosure-telemetry.test.ts +183 -0
  1122. package/src/recall-xray-renderer.test.ts +539 -0
  1123. package/src/recall-xray-renderer.ts +487 -0
  1124. package/src/recall-xray.test.ts +503 -0
  1125. package/src/recall-xray.ts +621 -0
  1126. package/src/reconstruct.ts +41 -0
  1127. package/src/release-changelog.ts +35 -0
  1128. package/src/relevance.ts +67 -0
  1129. package/src/replay/normalizers/chatgpt.ts +133 -0
  1130. package/src/replay/normalizers/claude.ts +102 -0
  1131. package/src/replay/normalizers/openclaw.ts +119 -0
  1132. package/src/replay/normalizers/shared.ts +69 -0
  1133. package/src/replay/runner.ts +197 -0
  1134. package/src/replay/types.ts +143 -0
  1135. package/src/rerank.test.ts +48 -0
  1136. package/src/rerank.ts +176 -0
  1137. package/src/resolve-auth-token.test.ts +226 -0
  1138. package/src/resolve-auth-token.ts +151 -0
  1139. package/src/resolve-provider-secret.test.ts +187 -0
  1140. package/src/resolve-provider-secret.ts +410 -0
  1141. package/src/response-guidance-recall.test.ts +3952 -0
  1142. package/src/response-guidance-recall.ts +4431 -0
  1143. package/src/resume-bundles.ts +415 -0
  1144. package/src/retrieval-agents.ts +623 -0
  1145. package/src/retrieval-tiers.ts +25 -0
  1146. package/src/retrieval.ts +104 -0
  1147. package/src/review/index.test.ts +201 -0
  1148. package/src/review/index.ts +536 -0
  1149. package/src/routing/engine.ts +162 -0
  1150. package/src/routing/store.ts +321 -0
  1151. package/src/runtime/better-sqlite.test.ts +32 -0
  1152. package/src/runtime/better-sqlite.ts +76 -0
  1153. package/src/runtime/child-process.ts +67 -0
  1154. package/src/runtime/env.ts +48 -0
  1155. package/src/sanitize.ts +58 -0
  1156. package/src/schemas.ts +449 -0
  1157. package/src/sdk-compat.ts +87 -0
  1158. package/src/search/document-scanner.ts +96 -0
  1159. package/src/search/embed-helper.ts +142 -0
  1160. package/src/search/factory.ts +189 -0
  1161. package/src/search/index.ts +10 -0
  1162. package/src/search/lancedb-backend.ts +342 -0
  1163. package/src/search/meilisearch-backend.ts +232 -0
  1164. package/src/search/noop-backend.ts +57 -0
  1165. package/src/search/orama-backend.ts +358 -0
  1166. package/src/search/port.ts +86 -0
  1167. package/src/search/remote-backend.ts +124 -0
  1168. package/src/secure-store/cipher.ts +271 -0
  1169. package/src/secure-store/cli-handlers.ts +355 -0
  1170. package/src/secure-store/cli-renderer.ts +131 -0
  1171. package/src/secure-store/header.ts +373 -0
  1172. package/src/secure-store/index.ts +137 -0
  1173. package/src/secure-store/kdf.ts +263 -0
  1174. package/src/secure-store/keyring.ts +106 -0
  1175. package/src/secure-store/metadata.ts +394 -0
  1176. package/src/secure-store/passphrase-reader.ts +252 -0
  1177. package/src/secure-store/secure-fs.ts +571 -0
  1178. package/src/secure-store/secure-store.test.ts +755 -0
  1179. package/src/semantic-chunking.ts +545 -0
  1180. package/src/semantic-consolidation.test.ts +182 -0
  1181. package/src/semantic-consolidation.ts +432 -0
  1182. package/src/semantic-rule-promotion.ts +183 -0
  1183. package/src/semantic-rule-verifier.ts +160 -0
  1184. package/src/session-integrity.ts +569 -0
  1185. package/src/session-observer-bands.ts +11 -0
  1186. package/src/session-observer-state.ts +346 -0
  1187. package/src/session-toggles.test.ts +96 -0
  1188. package/src/session-toggles.ts +159 -0
  1189. package/src/shared-context/manager.ts +810 -0
  1190. package/src/signal.ts +84 -0
  1191. package/src/skills-registry.test.ts +277 -0
  1192. package/src/skills-registry.ts +120 -0
  1193. package/src/source-attribution-roundtrip.test.ts +215 -0
  1194. package/src/source-attribution.test.ts +1425 -0
  1195. package/src/source-attribution.ts +639 -0
  1196. package/src/spaces/index.ts +627 -0
  1197. package/src/storage-paths.ts +117 -0
  1198. package/src/storage.ts +6657 -0
  1199. package/src/store-contract.ts +55 -0
  1200. package/src/summarizer.ts +844 -0
  1201. package/src/summary-snapshot.test.ts +681 -0
  1202. package/src/summary-snapshot.ts +238 -0
  1203. package/src/surfaces/dreams.test.ts +394 -0
  1204. package/src/surfaces/dreams.ts +346 -0
  1205. package/src/surfaces/heartbeat.test.ts +415 -0
  1206. package/src/surfaces/heartbeat.ts +325 -0
  1207. package/src/sync/index.ts +308 -0
  1208. package/src/targeted-fact-recall.test.ts +1694 -0
  1209. package/src/targeted-fact-recall.ts +2905 -0
  1210. package/src/taxonomy/default-taxonomy.ts +87 -0
  1211. package/src/taxonomy/index.ts +26 -0
  1212. package/src/taxonomy/resolver-doc-generator.ts +57 -0
  1213. package/src/taxonomy/resolver.ts +184 -0
  1214. package/src/taxonomy/taxonomy-loader.ts +186 -0
  1215. package/src/taxonomy/types.ts +48 -0
  1216. package/src/telemetry-transcript.ts +70 -0
  1217. package/src/temporal-index.ts +890 -0
  1218. package/src/temporal-supersession.test.ts +2703 -0
  1219. package/src/temporal-supersession.ts +493 -0
  1220. package/src/temporal-validity.test.ts +448 -0
  1221. package/src/temporal-validity.ts +123 -0
  1222. package/src/threading.ts +395 -0
  1223. package/src/tier-migration.ts +124 -0
  1224. package/src/tier-routing.ts +102 -0
  1225. package/src/tmt.ts +462 -0
  1226. package/src/tokens.test.ts +178 -0
  1227. package/src/tokens.ts +279 -0
  1228. package/src/topics.ts +147 -0
  1229. package/src/training-export/cli-date-validation.test.ts +258 -0
  1230. package/src/training-export/converter.test.ts +452 -0
  1231. package/src/training-export/converter.ts +319 -0
  1232. package/src/training-export/date-parse.ts +117 -0
  1233. package/src/training-export/index.ts +26 -0
  1234. package/src/training-export/registry.test.ts +85 -0
  1235. package/src/training-export/registry.ts +57 -0
  1236. package/src/training-export/types.ts +31 -0
  1237. package/src/transcript.ts +1179 -0
  1238. package/src/transfer/autodetect.ts +30 -0
  1239. package/src/transfer/backup.ts +138 -0
  1240. package/src/transfer/capsule-crypto.ts +485 -0
  1241. package/src/transfer/capsule-encrypt.test.ts +690 -0
  1242. package/src/transfer/capsule-export.ts +543 -0
  1243. package/src/transfer/capsule-fork.ts +375 -0
  1244. package/src/transfer/capsule-import.ts +564 -0
  1245. package/src/transfer/capsule-merge.ts +433 -0
  1246. package/src/transfer/conflict-policy.ts +16 -0
  1247. package/src/transfer/constants.ts +13 -0
  1248. package/src/transfer/exclusions.ts +37 -0
  1249. package/src/transfer/export-json.ts +65 -0
  1250. package/src/transfer/export-md.ts +59 -0
  1251. package/src/transfer/export-sqlite.ts +52 -0
  1252. package/src/transfer/fs-utils.ts +269 -0
  1253. package/src/transfer/import-json.ts +108 -0
  1254. package/src/transfer/import-md.ts +84 -0
  1255. package/src/transfer/import-sqlite.ts +100 -0
  1256. package/src/transfer/integrity.ts +71 -0
  1257. package/src/transfer/sqlite-schema.ts +16 -0
  1258. package/src/transfer/types.ts +297 -0
  1259. package/src/trust-zones.ts +1186 -0
  1260. package/src/types.ts +3074 -0
  1261. package/src/user-model.test.ts +124 -0
  1262. package/src/user-model.ts +162 -0
  1263. package/src/utility-learner.ts +353 -0
  1264. package/src/utility-runtime.ts +88 -0
  1265. package/src/utility-telemetry.ts +215 -0
  1266. package/src/utils/category-dir.ts +44 -0
  1267. package/src/utils/errno.ts +6 -0
  1268. package/src/utils/iso-timestamp.test.ts +37 -0
  1269. package/src/utils/iso-timestamp.ts +164 -0
  1270. package/src/utils/path.ts +26 -0
  1271. package/src/verified-recall.ts +138 -0
  1272. package/src/version-utils.test.ts +10 -0
  1273. package/src/version-utils.ts +9 -0
  1274. package/src/whitespace.ts +10 -0
  1275. package/src/work/board.ts +359 -0
  1276. package/src/work/boundary.ts +107 -0
  1277. package/src/work/storage.ts +436 -0
  1278. package/src/work/types.ts +82 -0
  1279. package/src/work-product-ledger.ts +265 -0
  1280. package/dist/access-service-BkXt3di1.d.ts +0 -2039
  1281. package/dist/capsule-crypto-SJS5VVAP.js +0 -18
  1282. package/dist/capsule-export-LLEVB2RG.js +0 -17
  1283. package/dist/capsule-import-UW45R2MZ.js +0 -16
  1284. package/dist/capsule-merge-DI7PNQ2H.js +0 -189
  1285. package/dist/chunk-2LGMW3DJ.js +0 -111
  1286. package/dist/chunk-2YMTO4ZJ.js +0 -265
  1287. package/dist/chunk-2YMTO4ZJ.js.map +0 -1
  1288. package/dist/chunk-363MWCD3.js +0 -9683
  1289. package/dist/chunk-363MWCD3.js.map +0 -1
  1290. package/dist/chunk-36CTNQY7.js +0 -1554
  1291. package/dist/chunk-36CTNQY7.js.map +0 -1
  1292. package/dist/chunk-457A4P3L.js +0 -119
  1293. package/dist/chunk-457A4P3L.js.map +0 -1
  1294. package/dist/chunk-4DXC6HQQ.js +0 -1837
  1295. package/dist/chunk-4DXC6HQQ.js.map +0 -1
  1296. package/dist/chunk-4IS4SXIQ.js +0 -2040
  1297. package/dist/chunk-57QNCUEZ.js +0 -1914
  1298. package/dist/chunk-57QNCUEZ.js.map +0 -1
  1299. package/dist/chunk-6AUUAZEX.js +0 -150
  1300. package/dist/chunk-6AUUAZEX.js.map +0 -1
  1301. package/dist/chunk-6TBWYBJ3.js +0 -236
  1302. package/dist/chunk-6XA7UN4Z.js +0 -135
  1303. package/dist/chunk-6Z6UH6TK.js +0 -2129
  1304. package/dist/chunk-6Z6UH6TK.js.map +0 -1
  1305. package/dist/chunk-74EMIVE4.js +0 -329
  1306. package/dist/chunk-74EMIVE4.js.map +0 -1
  1307. package/dist/chunk-74WWN7ZW.js +0 -82
  1308. package/dist/chunk-74WWN7ZW.js.map +0 -1
  1309. package/dist/chunk-767ODGE6.js +0 -183
  1310. package/dist/chunk-A4ACKWIW.js +0 -289
  1311. package/dist/chunk-A4ACKWIW.js.map +0 -1
  1312. package/dist/chunk-ASAITVLA.js +0 -64
  1313. package/dist/chunk-ASAITVLA.js.map +0 -1
  1314. package/dist/chunk-C5HUWVH2.js +0 -891
  1315. package/dist/chunk-C5HUWVH2.js.map +0 -1
  1316. package/dist/chunk-D54LZC5L.js +0 -147
  1317. package/dist/chunk-DF3RVK3X.js +0 -119
  1318. package/dist/chunk-DF3RVK3X.js.map +0 -1
  1319. package/dist/chunk-E6K4NIEU.js +0 -747
  1320. package/dist/chunk-E6K4NIEU.js.map +0 -1
  1321. package/dist/chunk-EEQLFRUM.js +0 -89
  1322. package/dist/chunk-EQINRHYR.js +0 -672
  1323. package/dist/chunk-EQINRHYR.js.map +0 -1
  1324. package/dist/chunk-ETOW6ACV.js +0 -158
  1325. package/dist/chunk-ETOW6ACV.js.map +0 -1
  1326. package/dist/chunk-EYNQTST2.js +0 -721
  1327. package/dist/chunk-FYIYMQ5N.js +0 -221
  1328. package/dist/chunk-FYIYMQ5N.js.map +0 -1
  1329. package/dist/chunk-G2WADRQ3.js +0 -219
  1330. package/dist/chunk-G4SK7DSQ.js +0 -121
  1331. package/dist/chunk-GGD5W7TB.js +0 -105
  1332. package/dist/chunk-GGD5W7TB.js.map +0 -1
  1333. package/dist/chunk-GVPWB7EY.js +0 -390
  1334. package/dist/chunk-GVPWB7EY.js.map +0 -1
  1335. package/dist/chunk-HJYHRE4S.js +0 -647
  1336. package/dist/chunk-I6BQZSML.js +0 -1451
  1337. package/dist/chunk-I6BQZSML.js.map +0 -1
  1338. package/dist/chunk-IBX3VFOM.js +0 -446
  1339. package/dist/chunk-IBX3VFOM.js.map +0 -1
  1340. package/dist/chunk-IXEJRKCZ.js +0 -18
  1341. package/dist/chunk-JBMSGZEQ.js +0 -441
  1342. package/dist/chunk-JBMSGZEQ.js.map +0 -1
  1343. package/dist/chunk-JRNQ3RNA.js +0 -284
  1344. package/dist/chunk-JRNQ3RNA.js.map +0 -1
  1345. package/dist/chunk-K6WK37A6.js +0 -865
  1346. package/dist/chunk-K6WK37A6.js.map +0 -1
  1347. package/dist/chunk-KBYWQWSB.js +0 -271
  1348. package/dist/chunk-KUHRUM6B.js +0 -14397
  1349. package/dist/chunk-KUHRUM6B.js.map +0 -1
  1350. package/dist/chunk-KWBPHZUU.js +0 -83
  1351. package/dist/chunk-KWBPHZUU.js.map +0 -1
  1352. package/dist/chunk-LIO5X3CM.js +0 -596
  1353. package/dist/chunk-MARWOCVP.js +0 -48
  1354. package/dist/chunk-MCC6KDQF.js +0 -5095
  1355. package/dist/chunk-MCC6KDQF.js.map +0 -1
  1356. package/dist/chunk-N5AKDXAI.js +0 -74
  1357. package/dist/chunk-NN3LPQ5D.js +0 -936
  1358. package/dist/chunk-NN3LPQ5D.js.map +0 -1
  1359. package/dist/chunk-O4XJUPSF.js +0 -533
  1360. package/dist/chunk-O4XJUPSF.js.map +0 -1
  1361. package/dist/chunk-OA3L7BFR.js +0 -183
  1362. package/dist/chunk-OA3L7BFR.js.map +0 -1
  1363. package/dist/chunk-OR64ZGRZ.js +0 -23
  1364. package/dist/chunk-P73JTV34.js +0 -275
  1365. package/dist/chunk-P73JTV34.js.map +0 -1
  1366. package/dist/chunk-P77UEOU2.js +0 -1521
  1367. package/dist/chunk-P77UEOU2.js.map +0 -1
  1368. package/dist/chunk-PB5KW5PL.js +0 -118
  1369. package/dist/chunk-PHNGXFQ6.js +0 -623
  1370. package/dist/chunk-PHNGXFQ6.js.map +0 -1
  1371. package/dist/chunk-QIGOEM65.js +0 -228
  1372. package/dist/chunk-RXTFCYQF.js +0 -108
  1373. package/dist/chunk-S2JJBLJG.js +0 -2101
  1374. package/dist/chunk-S2JJBLJG.js.map +0 -1
  1375. package/dist/chunk-S3IP6R6K.js +0 -219
  1376. package/dist/chunk-S3IP6R6K.js.map +0 -1
  1377. package/dist/chunk-SRBJUAMP.js +0 -403
  1378. package/dist/chunk-SRBJUAMP.js.map +0 -1
  1379. package/dist/chunk-URB2WSKZ.js +0 -350
  1380. package/dist/chunk-URB2WSKZ.js.map +0 -1
  1381. package/dist/chunk-VQXK37XA.js +0 -26
  1382. package/dist/chunk-VQXK37XA.js.map +0 -1
  1383. package/dist/chunk-VTU2B4VF.js +0 -146
  1384. package/dist/chunk-VTU2B4VF.js.map +0 -1
  1385. package/dist/chunk-VX2IUQFE.js +0 -613
  1386. package/dist/chunk-VX2IUQFE.js.map +0 -1
  1387. package/dist/chunk-WGK4VHGP.js +0 -4292
  1388. package/dist/chunk-WGK4VHGP.js.map +0 -1
  1389. package/dist/chunk-WTFWLUSX.js +0 -827
  1390. package/dist/chunk-WTFWLUSX.js.map +0 -1
  1391. package/dist/chunk-XJKFSSDW.js +0 -726
  1392. package/dist/chunk-XJKFSSDW.js.map +0 -1
  1393. package/dist/chunk-XMHBH5H6.js +0 -283
  1394. package/dist/chunk-XMHBH5H6.js.map +0 -1
  1395. package/dist/chunk-XMVFHBHT.js +0 -277
  1396. package/dist/chunk-Y5KDIOKF.js +0 -2403
  1397. package/dist/chunk-Y5KDIOKF.js.map +0 -1
  1398. package/dist/chunk-YNB73F22.js +0 -137
  1399. package/dist/chunk-YNB73F22.js.map +0 -1
  1400. package/dist/chunk-Z2E7VW55.js +0 -335
  1401. package/dist/chunk-Z2E7VW55.js.map +0 -1
  1402. package/dist/chunk-Z5S5HNGY.js +0 -2280
  1403. package/dist/chunk-Z5S5HNGY.js.map +0 -1
  1404. package/dist/chunk-ZL4S7ARC.js +0 -53
  1405. package/dist/chunk-ZTSE2ZJ6.js +0 -190
  1406. package/dist/chunk-ZTSE2ZJ6.js.map +0 -1
  1407. package/dist/cli-Cvy2SNhF.d.ts +0 -1259
  1408. package/dist/codex-materialize-CQlLTzke.d.ts +0 -139
  1409. package/dist/connectors-cli-DFGtY2DB.d.ts +0 -257
  1410. package/dist/contradiction-review-5LTTVDQV.js +0 -22
  1411. package/dist/contradiction-scan-3Z6YW7YA.js +0 -413
  1412. package/dist/contradiction-scan-3Z6YW7YA.js.map +0 -1
  1413. package/dist/engine-FOC3IJLA.js +0 -28
  1414. package/dist/fs-utils-IRVUFB6G.js +0 -30
  1415. package/dist/graph-edge-decay-PWB63GRE.js +0 -207
  1416. package/dist/index-1qIcnbG1.d.ts +0 -34
  1417. package/dist/memory-governance-F3QOJGEY.js +0 -37
  1418. package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
  1419. package/dist/orchestrator-AOQMo7QI.d.ts +0 -1784
  1420. package/dist/path-RMTY5Y5A.js +0 -9
  1421. package/dist/port-B6VEDIkC.d.ts +0 -53
  1422. package/dist/resolution-YGIBORXI.js +0 -101
  1423. package/dist/resolution-YGIBORXI.js.map +0 -1
  1424. package/dist/secure-store-4R2GSO7S.js +0 -156
  1425. package/dist/semantic-consolidation-ByBXb-sf.d.ts +0 -180
  1426. package/dist/state-store-3EH7HYIN.js +0 -16
  1427. package/dist/types-V3FJ26TF.js +0 -30
  1428. /package/dist/{capsule-crypto-SJS5VVAP.js.map → action-confidence.js.map} +0 -0
  1429. /package/dist/{capsule-export-LLEVB2RG.js.map → adapters/claude-code.js.map} +0 -0
  1430. /package/dist/{capsule-import-UW45R2MZ.js.map → adapters/codex.js.map} +0 -0
  1431. /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/hermes.js.map} +0 -0
  1432. /package/dist/{engine-FOC3IJLA.js.map → adapters/index.js.map} +0 -0
  1433. /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/registry.js.map} +0 -0
  1434. /package/dist/{memory-governance-F3QOJGEY.js.map → adapters/replit.js.map} +0 -0
  1435. /package/dist/{path-RMTY5Y5A.js.map → adapters/types.js.map} +0 -0
  1436. /package/dist/{secure-store-4R2GSO7S.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
  1437. /package/dist/{capsule-merge-DI7PNQ2H.js.map → capsule-merge-4MGKE7C5.js.map} +0 -0
  1438. /package/dist/{chunk-G4SK7DSQ.js.map → chunk-2WWLHTZY.js.map} +0 -0
  1439. /package/dist/{chunk-KBYWQWSB.js.map → chunk-4CRG46BG.js.map} +0 -0
  1440. /package/dist/{chunk-LIO5X3CM.js.map → chunk-7IASACLB.js.map} +0 -0
  1441. /package/dist/{chunk-EYNQTST2.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
  1442. /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
  1443. /package/dist/{chunk-QIGOEM65.js.map → chunk-GGKRUQOO.js.map} +0 -0
  1444. /package/dist/{chunk-HJYHRE4S.js.map → chunk-GL6I6MEQ.js.map} +0 -0
  1445. /package/dist/{state-store-3EH7HYIN.js.map → chunk-HHLLAQGZ.js.map} +0 -0
  1446. /package/dist/{chunk-4IS4SXIQ.js.map → chunk-HXXBL2KD.js.map} +0 -0
  1447. /package/dist/{chunk-767ODGE6.js.map → chunk-KNKUID7G.js.map} +0 -0
  1448. /package/dist/{chunk-6TBWYBJ3.js.map → chunk-LPMVBPA3.js.map} +0 -0
  1449. /package/dist/{chunk-PB5KW5PL.js.map → chunk-MC26UJIM.js.map} +0 -0
  1450. /package/dist/{chunk-ZL4S7ARC.js.map → chunk-MT4HVDUZ.js.map} +0 -0
  1451. /package/dist/{chunk-G2WADRQ3.js.map → chunk-MY6TPVXW.js.map} +0 -0
  1452. /package/dist/{chunk-OR64ZGRZ.js.map → chunk-NNVTUXEB.js.map} +0 -0
  1453. /package/dist/{chunk-RXTFCYQF.js.map → chunk-P4NEIHUT.js.map} +0 -0
  1454. /package/dist/{chunk-IXEJRKCZ.js.map → chunk-QRNI5JBH.js.map} +0 -0
  1455. /package/dist/{chunk-EEQLFRUM.js.map → chunk-RRF5UOBJ.js.map} +0 -0
  1456. /package/dist/{types-V3FJ26TF.js.map → chunk-SEDEKFYQ.js.map} +0 -0
  1457. /package/dist/{chunk-2LGMW3DJ.js.map → chunk-U3PN77QT.js.map} +0 -0
  1458. /package/dist/{chunk-XMVFHBHT.js.map → chunk-U3WSW6PZ.js.map} +0 -0
  1459. /package/dist/{chunk-N5AKDXAI.js.map → chunk-UWVJF25J.js.map} +0 -0
  1460. /package/dist/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
  1461. /package/dist/{chunk-6XA7UN4Z.js.map → chunk-XVZ7B3HG.js.map} +0 -0
  1462. /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
+ });