@remnic/core 1.1.12 → 1.1.13

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