@rubytech/create-maxy-code 0.1.356 → 0.1.358

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 (256) hide show
  1. package/dist/__tests__/claude-ptys-slice.test.js +36 -1
  2. package/dist/port-resolution.js +18 -1
  3. package/package.json +1 -1
  4. package/payload/platform/package-lock.json +237 -0
  5. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js +79 -1
  6. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js.map +1 -1
  7. package/payload/platform/plugins/admin/mcp/dist/index.js +17 -3
  8. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  9. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts +16 -0
  10. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts.map +1 -1
  11. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js +52 -0
  12. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js.map +1 -1
  13. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +59 -1
  14. package/payload/platform/plugins/cloudflare/PLUGIN.md +7 -1
  15. package/payload/platform/plugins/cloudflare/bin/__tests__/cf-token.test.sh +195 -0
  16. package/payload/platform/plugins/cloudflare/bin/cf-token.sh +157 -0
  17. package/payload/platform/plugins/cloudflare/references/api.md +2 -0
  18. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +14 -0
  19. package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +10 -1
  20. package/payload/platform/plugins/docs/PLUGIN.md +2 -0
  21. package/payload/platform/plugins/docs/references/claudeai-connectors.md +45 -0
  22. package/payload/platform/plugins/docs/references/cloudflare.md +6 -0
  23. package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
  24. package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
  25. package/payload/platform/plugins/email/PLUGIN.md +7 -3
  26. package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.d.ts +2 -0
  27. package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.d.ts.map +1 -0
  28. package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.js +99 -0
  29. package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.js.map +1 -0
  30. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.d.ts +2 -0
  31. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.js +32 -0
  33. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.js.map +1 -0
  34. package/payload/platform/plugins/email/mcp/dist/__tests__/credentials.test.d.ts +2 -0
  35. package/payload/platform/plugins/email/mcp/dist/__tests__/credentials.test.d.ts.map +1 -0
  36. package/payload/platform/plugins/email/mcp/dist/__tests__/credentials.test.js +109 -0
  37. package/payload/platform/plugins/email/mcp/dist/__tests__/credentials.test.js.map +1 -0
  38. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-edit.test.js +64 -6
  39. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-edit.test.js.map +1 -1
  40. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft.test.js +12 -0
  41. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft.test.js.map +1 -1
  42. package/payload/platform/plugins/email/mcp/dist/__tests__/email-setup.test.js +22 -0
  43. package/payload/platform/plugins/email/mcp/dist/__tests__/email-setup.test.js.map +1 -1
  44. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +94 -9
  45. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  46. package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.js +4 -0
  47. package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.js.map +1 -1
  48. package/payload/platform/plugins/email/mcp/dist/index.js +39 -2
  49. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  50. package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts +5 -0
  51. package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts.map +1 -1
  52. package/payload/platform/plugins/email/mcp/dist/lib/compose.js +8 -1
  53. package/payload/platform/plugins/email/mcp/dist/lib/compose.js.map +1 -1
  54. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts +14 -7
  55. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -1
  56. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +87 -31
  57. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -1
  58. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +68 -33
  59. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  60. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +253 -97
  61. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  62. package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts +6 -0
  63. package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts.map +1 -1
  64. package/payload/platform/plugins/email/mcp/dist/lib/smtp.js +4 -0
  65. package/payload/platform/plugins/email/mcp/dist/lib/smtp.js.map +1 -1
  66. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.d.ts +8 -1
  67. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.d.ts.map +1 -1
  68. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.js +10 -6
  69. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.js.map +1 -1
  70. package/payload/platform/plugins/email/mcp/dist/tools/email-draft.d.ts.map +1 -1
  71. package/payload/platform/plugins/email/mcp/dist/tools/email-draft.js +4 -2
  72. package/payload/platform/plugins/email/mcp/dist/tools/email-draft.js.map +1 -1
  73. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.d.ts +15 -0
  74. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.d.ts.map +1 -0
  75. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.js +29 -0
  76. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.js.map +1 -0
  77. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +1 -1
  78. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
  79. package/payload/platform/plugins/email/mcp/dist/tools/email-status.d.ts.map +1 -1
  80. package/payload/platform/plugins/email/mcp/dist/tools/email-status.js +2 -1
  81. package/payload/platform/plugins/email/mcp/dist/tools/email-status.js.map +1 -1
  82. package/payload/platform/plugins/email/mcp/package.json +2 -0
  83. package/payload/platform/plugins/email/references/email-reference.md +21 -1
  84. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-bootstrap.test.d.ts +2 -0
  85. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-bootstrap.test.d.ts.map +1 -0
  86. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-bootstrap.test.js +60 -0
  87. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-bootstrap.test.js.map +1 -0
  88. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +11 -1
  89. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  90. package/payload/platform/scripts/rss-sampler.sh +21 -0
  91. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  92. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  93. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  94. package/payload/platform/services/claude-session-manager/dist/connector-auth.d.ts +31 -0
  95. package/payload/platform/services/claude-session-manager/dist/connector-auth.d.ts.map +1 -0
  96. package/payload/platform/services/claude-session-manager/dist/connector-auth.js +49 -0
  97. package/payload/platform/services/claude-session-manager/dist/connector-auth.js.map +1 -0
  98. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +24 -0
  99. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  100. package/payload/platform/services/claude-session-manager/dist/http-server.js +214 -17
  101. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  102. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +2 -1
  103. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  104. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +24 -4
  105. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  106. package/payload/platform/services/webchat-channel/dist/instructions.d.ts +1 -0
  107. package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
  108. package/payload/platform/services/webchat-channel/dist/instructions.js +28 -0
  109. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  110. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  111. package/payload/platform/services/webchat-channel/dist/server.js +75 -5
  112. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  113. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  114. package/payload/server/{chunk-25UVZNFA.js → chunk-CXEC5RWB.js} +26 -3
  115. package/payload/server/maxy-edge.js +1 -1
  116. package/payload/server/public/assets/{AdminLoginScreens-4I5CdIWl.js → AdminLoginScreens-Ro_RUTDh.js} +1 -1
  117. package/payload/server/public/assets/{AdminShell-Bc9F7X_N.js → AdminShell-CmqhpAPU.js} +1 -1
  118. package/payload/server/public/assets/{Checkbox-DoQV18Ow.js → Checkbox-dYLaMEdh.js} +1 -1
  119. package/payload/server/public/assets/OperatorConversations-2zJh7qnO.css +1 -0
  120. package/payload/server/public/assets/OperatorConversations-Cei12sSK.js +9 -0
  121. package/payload/server/public/assets/{admin-DLsMA09F.js → admin-BldZQrJF.js} +1 -1
  122. package/payload/server/public/assets/{arc-DpkSQNto.js → arc-Crkz45LN.js} +1 -1
  123. package/payload/server/public/assets/architecture-YZFGNWBL-Dma4Bgha.js +1 -0
  124. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Bw7Yq8O9.js → architectureDiagram-Q4EWVU46-C9JThHgP.js} +1 -1
  125. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BBtJQFSz.js → blockDiagram-DXYQGD6D-DpsV_HXT.js} +1 -1
  126. package/payload/server/public/assets/{browser-w6rY_KAO.js → browser-DrXZ4wpy.js} +1 -1
  127. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-JAMgxzOr.js → c4Diagram-AHTNJAMY-CTm_6aKi.js} +1 -1
  128. package/payload/server/public/assets/{calendar-CvEdDFYd.js → calendar-CQ4TCbO7.js} +1 -1
  129. package/payload/server/public/assets/channel-B_-5bau1.js +1 -0
  130. package/payload/server/public/assets/chat-D7w6_NE1.js +1 -0
  131. package/payload/server/public/assets/{chunk-2KRD3SAO-eDugsc2L.js → chunk-2KRD3SAO-1KSJW1q6.js} +1 -1
  132. package/payload/server/public/assets/{chunk-336JU56O-BXfECwdC.js → chunk-336JU56O-C3t08Ip-.js} +2 -2
  133. package/payload/server/public/assets/chunk-426QAEUC-Dirxdwjh.js +1 -0
  134. package/payload/server/public/assets/{chunk-4BX2VUAB-CByVeKCY.js → chunk-4BX2VUAB-kl1ze33n.js} +1 -1
  135. package/payload/server/public/assets/{chunk-4TB4RGXK-CpY0DcUB.js → chunk-4TB4RGXK-DxpoDoHg.js} +1 -1
  136. package/payload/server/public/assets/{chunk-55IACEB6-C3YxgyIb.js → chunk-55IACEB6-WM9VJzha.js} +1 -1
  137. package/payload/server/public/assets/{chunk-5FUZZQ4R-Bc0d7j8A.js → chunk-5FUZZQ4R-CLH63ZH3.js} +1 -1
  138. package/payload/server/public/assets/{chunk-5PVQY5BW-CTUGMRIa.js → chunk-5PVQY5BW-DCTb8IAT.js} +1 -1
  139. package/payload/server/public/assets/{chunk-67CJDMHE-CbdPnpLd.js → chunk-67CJDMHE-BSYzyaoH.js} +1 -1
  140. package/payload/server/public/assets/{chunk-7N4EOEYR-Cjl2kevz.js → chunk-7N4EOEYR-CarJ7O2P.js} +1 -1
  141. package/payload/server/public/assets/{chunk-AA7GKIK3-BgozIh5p.js → chunk-AA7GKIK3-DTNLyBpH.js} +1 -1
  142. package/payload/server/public/assets/{chunk-BSJP7CBP-6Ufq45NW.js → chunk-BSJP7CBP-BFmHflHY.js} +1 -1
  143. package/payload/server/public/assets/{chunk-CIAEETIT-BhhYuMnA.js → chunk-CIAEETIT-BpjG0IzU.js} +1 -1
  144. package/payload/server/public/assets/{chunk-EDXVE4YY-BfaJ3yDD.js → chunk-EDXVE4YY-2fSowhlW.js} +1 -1
  145. package/payload/server/public/assets/{chunk-ENJZ2VHE-C7-hIGVC.js → chunk-ENJZ2VHE-ClcranGV.js} +1 -1
  146. package/payload/server/public/assets/{chunk-FMBD7UC4-BQvvyocF.js → chunk-FMBD7UC4-DLq4BItQ.js} +1 -1
  147. package/payload/server/public/assets/{chunk-FOC6F5B3-C6rBDOxH.js → chunk-FOC6F5B3-B9QI3Guz.js} +1 -1
  148. package/payload/server/public/assets/{chunk-ICPOFSXX-BjOJvkVa.js → chunk-ICPOFSXX-BAr-Mv1Z.js} +2 -2
  149. package/payload/server/public/assets/{chunk-K5T4RW27-kKowRHhn.js → chunk-K5T4RW27-8PvAHasY.js} +1 -1
  150. package/payload/server/public/assets/{chunk-KGLVRYIC-bAEV_5eH.js → chunk-KGLVRYIC--aGaDRH5.js} +1 -1
  151. package/payload/server/public/assets/{chunk-LIHQZDEY-BLvsQAfg.js → chunk-LIHQZDEY-BrWj7mw2.js} +1 -1
  152. package/payload/server/public/assets/{chunk-ORNJ4GCN-BzYZfPZ7.js → chunk-ORNJ4GCN-kYQmkMTi.js} +1 -1
  153. package/payload/server/public/assets/{chunk-OYMX7WX6-CL7WRw42.js → chunk-OYMX7WX6-CIsryCWH.js} +1 -1
  154. package/payload/server/public/assets/chunk-QZHKN3VN-Duipo1kI.js +1 -0
  155. package/payload/server/public/assets/{chunk-U2HBQHQK-BdcVGjau.js → chunk-U2HBQHQK-68X653e3.js} +1 -1
  156. package/payload/server/public/assets/{chunk-X2U36JSP-C0CL_Jin.js → chunk-X2U36JSP-v3v9soX3.js} +1 -1
  157. package/payload/server/public/assets/{chunk-XPW4576I-CsV7CUxH.js → chunk-XPW4576I-CW5DPpop.js} +1 -1
  158. package/payload/server/public/assets/{chunk-YZCP3GAM-Cj0_fLkR.js → chunk-YZCP3GAM-B1VP7Rl2.js} +1 -1
  159. package/payload/server/public/assets/{chunk-ZZ45TVLE-DLuquyoY.js → chunk-ZZ45TVLE-DiJrujNY.js} +1 -1
  160. package/payload/server/public/assets/classDiagram-6PBFFD2Q-B3kX5SuB.js +1 -0
  161. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-D761c7Lr.js +1 -0
  162. package/payload/server/public/assets/clone-BrhFv6Tb.js +1 -0
  163. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-BU3EOLXX.js → cose-bilkent-S5V4N54A-BscXxLMc.js} +1 -1
  164. package/payload/server/public/assets/{dagre-KV5264BT-CoCAYhFc.js → dagre-KV5264BT-DVgGlTQ1.js} +1 -1
  165. package/payload/server/public/assets/{dagre-BRq8C9NU.js → dagre-MBv6WlCS.js} +1 -1
  166. package/payload/server/public/assets/data-Dc5XNWgF.js +1 -0
  167. package/payload/server/public/assets/{diagram-5BDNPKRD-DBy5GNaQ.js → diagram-5BDNPKRD-BmhCaO0a.js} +1 -1
  168. package/payload/server/public/assets/{diagram-G4DWMVQ6-l93MsBLv.js → diagram-G4DWMVQ6-ChU9L8vz.js} +1 -1
  169. package/payload/server/public/assets/{diagram-MMDJMWI5-BIfXkVlL.js → diagram-MMDJMWI5-Oy409Zc_.js} +1 -1
  170. package/payload/server/public/assets/{diagram-TYMM5635-BuTJLBny.js → diagram-TYMM5635-Cn1YByqk.js} +1 -1
  171. package/payload/server/public/assets/{erDiagram-SMLLAGMA-BwPxWDbP.js → erDiagram-SMLLAGMA-e17yfLvr.js} +1 -1
  172. package/payload/server/public/assets/{flatten-DtM0XAMt.js → flatten-Cl1Bc3dR.js} +1 -1
  173. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BhPtHXMR.js → flowDiagram-DWJPFMVM-Dqt7sCVZ.js} +1 -1
  174. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BlXjprdD.js → ganttDiagram-T4ZO3ILL-B4IhwS2r.js} +1 -1
  175. package/payload/server/public/assets/gitGraph-7Q5UKJZL-BCQAqtLy.js +1 -0
  176. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BW36BDiu.js → gitGraphDiagram-UUTBAWPF-BDw-PKcI.js} +1 -1
  177. package/payload/server/public/assets/{graph-BrwfYOr3.js → graph-l_3_j3I9.js} +3 -3
  178. package/payload/server/public/assets/{graph-labels-BNZ8u8TE.js → graph-labels-CJj55nHk.js} +1 -1
  179. package/payload/server/public/assets/{graphlib-Bjg3Tq-k.js → graphlib-DPIYk_sg.js} +1 -1
  180. package/payload/server/public/assets/info-OMHHGYJF-qU7hK50T.js +1 -0
  181. package/payload/server/public/assets/infoDiagram-42DDH7IO-KGdcJeTs.js +2 -0
  182. package/payload/server/public/assets/{isEmpty-D__iH1WQ.js → isEmpty-D1pGOD1J.js} +1 -1
  183. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CmB-bA_O.js → ishikawaDiagram-UXIWVN3A-BRIHsB3G.js} +1 -1
  184. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-Bt1fJKmc.js → journeyDiagram-VCZTEJTY-aKNgmoHd.js} +1 -1
  185. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DVbksz_J.js → kanban-definition-6JOO6SKY-cwBwT-nm.js} +1 -1
  186. package/payload/server/public/assets/{line-BX2Ugx3B.js → line-DVtVRzUA.js} +1 -1
  187. package/payload/server/public/assets/{linear-Bdc-QJWI.js → linear-DtoOFPO2.js} +1 -1
  188. package/payload/server/public/assets/{mermaid-parser.core-qv1e-J5x.js → mermaid-parser.core-G7YD_zO3.js} +2 -2
  189. package/payload/server/public/assets/{mermaid.core-C_d9YQX_.js → mermaid.core-DpFyndHm.js} +3 -3
  190. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DwKGvF14.js → mindmap-definition-QFDTVHPH-CYXK_Pvz.js} +1 -1
  191. package/payload/server/public/assets/{operator-BNY1P_mH.js → operator-CKmOgXNa.js} +1 -1
  192. package/payload/server/public/assets/{ordinal-BRQ5CzZY.js → ordinal-CBZeH4Yp.js} +1 -1
  193. package/payload/server/public/assets/packet-4T2RLAQJ-DiU2yS9m.js +1 -0
  194. package/payload/server/public/assets/page-hG7xTrcO.js +32 -0
  195. package/payload/server/public/assets/pie-ZZUOXDRM-zHUKKlVv.js +1 -0
  196. package/payload/server/public/assets/{pieDiagram-DEJITSTG-dUvQC_4S.js → pieDiagram-DEJITSTG-DFo4N6WH.js} +1 -1
  197. package/payload/server/public/assets/{public-CDzw6yjH.js → public-smrhkkxD.js} +1 -1
  198. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CebX_oPa.js → quadrantDiagram-34T5L4WZ-BlH5iIhl.js} +1 -1
  199. package/payload/server/public/assets/radar-PYXPWWZC-BUnVoaPg.js +1 -0
  200. package/payload/server/public/assets/{reduce-CkcXOmmJ.js → reduce-PuPlFPRX.js} +1 -1
  201. package/payload/server/public/assets/{requirementDiagram-MS252O5E-h2euIqGj.js → requirementDiagram-MS252O5E-D5WLty8A.js} +1 -1
  202. package/payload/server/public/assets/rotate-ccw-Dd8aAvU7.js +1 -0
  203. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DCii3fkF.js → sankeyDiagram-XADWPNL6-vB85IxHG.js} +1 -1
  204. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-CQSGYmlq.js → sequenceDiagram-FGHM5R23-Cxraa0Ee.js} +1 -1
  205. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-CVtU0iXH.js → stateDiagram-FHFEXIEX-BBPssYBx.js} +1 -1
  206. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BWmmUwDt.js +1 -0
  207. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-ChvsBltI.js → timeline-definition-GMOUNBTQ-Bgk6PT62.js} +1 -1
  208. package/payload/server/public/assets/treeView-SZITEDCU-C3Y-Q52h.js +1 -0
  209. package/payload/server/public/assets/treemap-W4RFUUIX-Dk0qZpAg.js +1 -0
  210. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-e-EZiiba.js → vennDiagram-DHZGUBPP-B-APTs3L.js} +1 -1
  211. package/payload/server/public/assets/wardley-RL74JXVD-BAWI02OA.js +1 -0
  212. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-4JaRn1s5.js → wardleyDiagram-NUSXRM2D-CkXJSs_i.js} +1 -1
  213. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-kxwmCNuM.js → xychartDiagram-5P7HB3ND-BkyshPMV.js} +1 -1
  214. package/payload/server/public/browser.html +4 -4
  215. package/payload/server/public/calendar.html +4 -4
  216. package/payload/server/public/chat.html +7 -6
  217. package/payload/server/public/data.html +4 -4
  218. package/payload/server/public/graph.html +7 -6
  219. package/payload/server/public/index.html +6 -6
  220. package/payload/server/public/operator.html +9 -8
  221. package/payload/server/public/public.html +7 -6
  222. package/payload/server/server.js +255 -26
  223. package/payload/server/userprofile-reconcile-GLGKDVQQ.js +48 -0
  224. package/payload/server/public/assets/OperatorConversations-BJY9FZbr.js +0 -9
  225. package/payload/server/public/assets/OperatorConversations-C1I78hON.css +0 -1
  226. package/payload/server/public/assets/architecture-YZFGNWBL-Csj53u5T.js +0 -1
  227. package/payload/server/public/assets/channel-BUQUAUts.js +0 -1
  228. package/payload/server/public/assets/chat-BlOWMUVI.js +0 -1
  229. package/payload/server/public/assets/chunk-426QAEUC-CdknReB2.js +0 -1
  230. package/payload/server/public/assets/chunk-QZHKN3VN-8tHoEDE9.js +0 -1
  231. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BEGZ3DLg.js +0 -1
  232. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DsLxnm5U.js +0 -1
  233. package/payload/server/public/assets/clone-uiXbH_kh.js +0 -1
  234. package/payload/server/public/assets/data-p3T_EZFq.js +0 -1
  235. package/payload/server/public/assets/gitGraph-7Q5UKJZL-D_2KrZoP.js +0 -1
  236. package/payload/server/public/assets/info-OMHHGYJF-BP2TNwQ4.js +0 -1
  237. package/payload/server/public/assets/infoDiagram-42DDH7IO-D1kDtBOY.js +0 -2
  238. package/payload/server/public/assets/packet-4T2RLAQJ-BDg8fde8.js +0 -1
  239. package/payload/server/public/assets/page-CtygoIXt.js +0 -32
  240. package/payload/server/public/assets/pie-ZZUOXDRM-CXDyYMkZ.js +0 -1
  241. package/payload/server/public/assets/radar-PYXPWWZC-xen_zv8F.js +0 -1
  242. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BywcXjtb.js +0 -1
  243. package/payload/server/public/assets/treeView-SZITEDCU-2FBf_1It.js +0 -1
  244. package/payload/server/public/assets/treemap-W4RFUUIX-BDdb30P-.js +0 -1
  245. package/payload/server/public/assets/wardley-RL74JXVD-BLq5MxjR.js +0 -1
  246. /package/payload/server/public/assets/{_baseFor-SRtUHe7G.js → _baseFor-brRjpUOX.js} +0 -0
  247. /package/payload/server/public/assets/{array-BpsnM-Py.js → array-BGFCBI0e.js} +0 -0
  248. /package/payload/server/public/assets/{cytoscape.esm-D4Rl1H5h.js → cytoscape.esm-TvRJ2tGX.js} +0 -0
  249. /package/payload/server/public/assets/{defaultLocale-C7sQPl-O.js → defaultLocale-CRZydyG6.js} +0 -0
  250. /package/payload/server/public/assets/{dist-CtUNt3w8.js → dist-B0CkUodR.js} +0 -0
  251. /package/payload/server/public/assets/{init-BydxaDEV.js → init-B8gtcn7T.js} +0 -0
  252. /package/payload/server/public/assets/{katex-B0fVeDx4.js → katex-RxgSu_dy.js} +0 -0
  253. /package/payload/server/public/assets/{path-jlWYQ2i9.js → path-DZF-JdEe.js} +0 -0
  254. /package/payload/server/public/assets/{preload-helper-uTix4PVD.js → preload-helper-CQ36nxok.js} +0 -0
  255. /package/payload/server/public/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
  256. /package/payload/server/public/assets/{src-48kCpScq.js → src-CR_MU8uy.js} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { ImapFlow } from "imapflow";
2
+ import { simpleParser } from "mailparser";
2
3
  import { trackProbe, releaseProbe, newProbeToken } from "./setup-probe.js";
3
4
  const CONNECT_TIMEOUT = 10_000;
4
5
  const COMMAND_TIMEOUT = 30_000;
@@ -22,6 +23,61 @@ async function safeLogout(client) {
22
23
  catch { /* best effort */ }
23
24
  }
24
25
  }
26
+ // ─── Transient transport-error retry for draft write cycles ─────────
27
+ // Gmail occasionally drops connect/append/expunge mid-flight ("Unexpected
28
+ // close", "Socket timeout", "Connection not available"). One drop should not
29
+ // fail an entire draft edit. These are the only signatures retried; auth and
30
+ // every other permanent error surface on the first occurrence.
31
+ const TRANSIENT_ERROR_PATTERNS = [
32
+ "Socket timeout",
33
+ "Unexpected close",
34
+ "Connection not available",
35
+ "ETIMEDOUT",
36
+ "ECONNRESET",
37
+ ];
38
+ /**
39
+ * Classify a transport error for retry. Returns the matched transient signature,
40
+ * or null when the error is permanent (auth/login) or otherwise not a known
41
+ * transient drop. Auth failures are screened out explicitly so a wrong password
42
+ * is never retried.
43
+ */
44
+ function transientClass(err) {
45
+ const msg = err instanceof Error ? err.message : String(err);
46
+ if (msg.includes("AUTHENTICATIONFAILED") ||
47
+ msg.includes("Invalid credentials") ||
48
+ msg.includes("LOGIN failed")) {
49
+ return null;
50
+ }
51
+ return TRANSIENT_ERROR_PATTERNS.find((p) => msg.includes(p)) ?? null;
52
+ }
53
+ /**
54
+ * Run a draft IMAP write cycle with a bounded retry that fires only for
55
+ * transient transport drops. `fn` opens its own client, so each attempt gets a
56
+ * fresh connection. At most two attempts: a single transient drop is absorbed;
57
+ * a persistent one (or any permanent error) is thrown.
58
+ *
59
+ * Atomicity note: the replace cycle appends before it expunges, so if a drop
60
+ * lands after the append the retry can leave two replacement copies. Two copies
61
+ * is the deliberate trade — it preserves the "never zero copies" contract that
62
+ * a lost reply would otherwise violate. The per-attempt log line marks when a
63
+ * retry re-ran a write cycle.
64
+ */
65
+ async function withTransientRetry(op, fn) {
66
+ const MAX_ATTEMPTS = 2;
67
+ for (let attempt = 1;; attempt++) {
68
+ try {
69
+ return await fn();
70
+ }
71
+ catch (err) {
72
+ const cls = transientClass(err);
73
+ if (attempt < MAX_ATTEMPTS && cls) {
74
+ diag(op, "retry", `attempt=${attempt}/${MAX_ATTEMPTS} reason=${cls}`);
75
+ continue;
76
+ }
77
+ throw err;
78
+ }
79
+ }
80
+ }
25
81
  // ─── Hard cap for tool-layer limit enforcement ──────────────────────
26
82
  export const MAX_LIMIT = 50;
27
83
  /**
@@ -80,10 +136,33 @@ export function stripHtml(html) {
80
136
  .replace(/\s+/g, " ")
81
137
  .trim();
82
138
  }
83
- /** Max bytes to fetch per message body (4KB is plenty for a 500-char preview). */
84
- const BODY_MAX_BYTES = 4096;
85
139
  /** Max characters for body preview in tool output. */
86
140
  const BODY_PREVIEW_LENGTH = 500;
141
+ /**
142
+ * Decode the readable text body of a raw RFC822 message with mailparser.
143
+ *
144
+ * Handles every MIME shape — flat text, single-part HTML, multipart/alternative,
145
+ * and nested multipart/related|mixed (Outlook's inline-image house style) — plus
146
+ * all transfer encodings and non-UTF-8 charsets. Returns the plain-text part when
147
+ * present, else the HTML part stripped to text, else the "(body unavailable)"
148
+ * sentinel. Never truncates; callers cap previews with previewBody.
149
+ */
150
+ export async function extractBody(source) {
151
+ const parsed = await simpleParser(source);
152
+ const text = parsed.text?.trim();
153
+ if (text)
154
+ return { body: text, kind: "text" };
155
+ const html = parsed.html ? stripHtml(parsed.html).trim() : "";
156
+ if (html)
157
+ return { body: html, kind: "html" };
158
+ return { body: "(body unavailable)", kind: "none" };
159
+ }
160
+ /** Cap a body to BODY_PREVIEW_LENGTH for list/triage output, appending an ellipsis. */
161
+ export function previewBody(body) {
162
+ return body.length > BODY_PREVIEW_LENGTH
163
+ ? body.slice(0, BODY_PREVIEW_LENGTH) + "…"
164
+ : body;
165
+ }
87
166
  /** Hard cap on a single attachment's bytes — over this size, metadata is surfaced
88
167
  * but bytes are not downloaded. Matches typical provider send limits. */
89
168
  export const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
@@ -220,70 +299,6 @@ export async function fetchAttachmentBytesForMessages(config, password, requests
220
299
  }
221
300
  return results;
222
301
  }
223
- /**
224
- * Decode a raw IMAP body part buffer according to its transfer encoding.
225
- * Returns a UTF-8 string. Falls back to raw UTF-8 for unknown encodings.
226
- */
227
- export function decodeBodyPart(buf, encoding) {
228
- const enc = encoding?.toLowerCase().trim();
229
- if (!enc || enc === "7bit" || enc === "8bit" || enc === "binary") {
230
- return buf.toString("utf-8");
231
- }
232
- if (enc === "base64") {
233
- return Buffer.from(buf.toString("ascii").replace(/\s/g, ""), "base64").toString("utf-8");
234
- }
235
- if (enc === "quoted-printable") {
236
- return buf
237
- .toString("ascii")
238
- .replace(/=\r?\n/g, "")
239
- .replace(/=([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
240
- }
241
- // Unknown encoding — best-effort UTF-8
242
- return buf.toString("utf-8");
243
- }
244
- /**
245
- * Extract a text body preview from a fetched message's inline bodyParts + bodyStructure.
246
- *
247
- * Determines the content type and transfer encoding of part "1" from
248
- * bodyStructure (single-part: structure itself, multipart: first childNode).
249
- * Skips non-text parts and multipart containers. Strips HTML. Truncates
250
- * to BODY_PREVIEW_LENGTH characters.
251
- */
252
- export function extractBodyFromFetch(msg) {
253
- const buf = msg.bodyParts?.get("1");
254
- if (!buf || buf.length === 0)
255
- return "(body unavailable)";
256
- const structure = msg.bodyStructure;
257
- if (!structure)
258
- return "(body unavailable)";
259
- // Determine content type and encoding for IMAP part "1":
260
- // - Single-part message: bodyStructure itself describes the body
261
- // - Multipart message: first childNode describes part "1"
262
- let type;
263
- let encoding;
264
- if (structure.childNodes && structure.childNodes.length > 0) {
265
- const firstChild = structure.childNodes[0];
266
- type = firstChild.type?.toLowerCase();
267
- encoding = firstChild.encoding;
268
- }
269
- else {
270
- type = structure.type?.toLowerCase();
271
- encoding = structure.encoding;
272
- }
273
- // Skip non-text content (binary attachments, images, nested multipart containers)
274
- if (!type || !type.startsWith("text/"))
275
- return "(body unavailable)";
276
- let text = decodeBodyPart(buf, encoding);
277
- if (type === "text/html") {
278
- text = stripHtml(text);
279
- }
280
- text = text.trim();
281
- if (!text)
282
- return "(body unavailable)";
283
- return text.length > BODY_PREVIEW_LENGTH
284
- ? text.slice(0, BODY_PREVIEW_LENGTH) + "\u2026"
285
- : text;
286
- }
287
302
  /**
288
303
  * Test IMAP connectivity and authentication.
289
304
  * Returns null on success, error message on failure.
@@ -491,11 +506,15 @@ export async function fetchRecent(config, password, options = {}) {
491
506
  let envelopeCount = 0;
492
507
  let recipientKept = 0;
493
508
  let recipientHidden = 0;
509
+ // Body sources captured during streaming and parsed after the fetch
510
+ // iterator closes — running simpleParser inside the open fetch loop can
511
+ // stall the IMAP socket. Aligned by index with `messages`.
512
+ const bodySources = [];
494
513
  for await (const msg of client.fetch(uidList, {
495
514
  envelope: true,
496
515
  uid: true,
497
516
  bodyStructure: true,
498
- bodyParts: [{ key: "1", start: 0, maxLength: BODY_MAX_BYTES }],
517
+ source: true,
499
518
  }, { uid: true })) {
500
519
  envelopeCount++;
501
520
  diag("fetchRecent", `envelope #${envelopeCount}`, `uid=${msg.uid} from=${msg.envelope?.from?.[0]?.address ?? "?"} at ${Date.now() - t2}ms`);
@@ -516,12 +535,14 @@ export async function fetchRecent(config, password, options = {}) {
516
535
  continue;
517
536
  // Collect up to `limit` messages for display, but keep iterating past
518
537
  // that point so the recipient kept/hidden accounting covers the whole
519
- // fetched window the envelopes are already fetched, so the extra
520
- // iterations cost nothing on the wire and we skip body extraction.
538
+ // fetched window. Body parsing is deferred until the iterator closes
539
+ // (see bodySources) and only runs for the displayed messages.
521
540
  if (messages.length < limit) {
522
- const bodyPreview = extractBodyFromFetch(msg);
523
- diag("fetchRecent", `body #${envelopeCount}`, `uid=${msg.uid} preview=${bodyPreview.length}chars type=${msg.bodyStructure?.type ?? "?"}`);
524
541
  const attachments = walkAttachmentMetadata(msg.bodyStructure);
542
+ bodySources.push({
543
+ source: msg.source ?? Buffer.alloc(0),
544
+ structureType: msg.bodyStructure?.type ?? "?",
545
+ });
525
546
  messages.push({
526
547
  uid: msg.uid,
527
548
  messageId,
@@ -529,11 +550,17 @@ export async function fetchRecent(config, password, options = {}) {
529
550
  from: fromAddr,
530
551
  subject,
531
552
  date,
532
- bodyPreview,
553
+ bodyPreview: "",
533
554
  attachments,
534
555
  });
535
556
  }
536
557
  }
558
+ // Parse the captured sources now that the fetch stream is closed.
559
+ for (let i = 0; i < messages.length; i++) {
560
+ const { body, kind } = await extractBody(bodySources[i].source);
561
+ messages[i].bodyPreview = previewBody(body);
562
+ diag("fetchRecent", `body #${i + 1}`, `uid=${messages[i].uid} preview=${messages[i].bodyPreview.length}chars type=${bodySources[i].structureType} bodySource=${kind}`);
563
+ }
537
564
  diag("fetchRecent", "fetch done", `${envelopeCount} envelopes, ${messages.length} messages in ${Date.now() - t2}ms (total ${Date.now() - t0}ms)`);
538
565
  if (clientSideRecipients) {
539
566
  diag("fetchRecent", "filter applied", `kept=${recipientKept} hidden=${recipientHidden} recipients=[${clientSideRecipients.join(",")}]`);
@@ -608,11 +635,14 @@ export async function searchMessages(config, password, query) {
608
635
  let envelopeCount = 0;
609
636
  let recipientKept = 0;
610
637
  let recipientHidden = 0;
638
+ // Body sources captured during streaming and parsed after the fetch
639
+ // iterator closes (see fetchRecent). Aligned by index with `messages`.
640
+ const bodySources = [];
611
641
  for await (const msg of client.fetch(recentUids.map(String).join(","), {
612
642
  envelope: true,
613
643
  uid: true,
614
644
  bodyStructure: true,
615
- bodyParts: [{ key: "1", start: 0, maxLength: BODY_MAX_BYTES }],
645
+ source: true,
616
646
  }, { uid: true })) {
617
647
  envelopeCount++;
618
648
  // Client-side recipient filter (replaces IMAP SEARCH TO), with kept-vs-hidden accounting
@@ -630,11 +660,13 @@ export async function searchMessages(config, password, query) {
630
660
  const inReplyTo = msg.envelope?.inReplyTo ?? null;
631
661
  // Collect up to `limit` messages for display, but keep iterating past
632
662
  // that point so the recipient kept/hidden accounting covers the whole
633
- // fetched window the envelopes are already fetched, so the extra
634
- // iterations cost nothing on the wire and we skip body extraction.
663
+ // fetched window. Body parsing is deferred until the iterator closes.
635
664
  if (messages.length < limit) {
636
- const bodyPreview = extractBodyFromFetch(msg);
637
665
  const attachments = walkAttachmentMetadata(msg.bodyStructure);
666
+ bodySources.push({
667
+ source: msg.source ?? Buffer.alloc(0),
668
+ structureType: msg.bodyStructure?.type ?? "?",
669
+ });
638
670
  messages.push({
639
671
  uid: msg.uid,
640
672
  messageId,
@@ -642,11 +674,17 @@ export async function searchMessages(config, password, query) {
642
674
  from: fromAddr,
643
675
  subject: subj,
644
676
  date,
645
- bodyPreview,
677
+ bodyPreview: "",
646
678
  attachments,
647
679
  });
648
680
  }
649
681
  }
682
+ // Parse the captured sources now that the fetch stream is closed.
683
+ for (let i = 0; i < messages.length; i++) {
684
+ const { body, kind } = await extractBody(bodySources[i].source);
685
+ messages[i].bodyPreview = previewBody(body);
686
+ diag("searchMessages", `body #${i + 1}`, `uid=${messages[i].uid} preview=${messages[i].bodyPreview.length}chars type=${bodySources[i].structureType} bodySource=${kind}`);
687
+ }
650
688
  diag("searchMessages", "fetch done", `${envelopeCount} envelopes, ${messages.length} messages in ${Date.now() - t2}ms (total ${Date.now() - t0}ms)`);
651
689
  if (clientSideRecipients) {
652
690
  diag("searchMessages", "filter applied", `kept=${recipientKept} hidden=${recipientHidden} recipients=[${clientSideRecipients.join(",")}]`);
@@ -728,11 +766,14 @@ export async function fetchSinceUid(config, password, sinceUid, recipients) {
728
766
  let hidden = 0;
729
767
  const t2 = Date.now();
730
768
  const uidList = uids.map(String).join(",");
769
+ // Body sources captured during streaming and parsed after the fetch
770
+ // iterator closes (see fetchRecent). Aligned by index with `emails`.
771
+ const bodySources = [];
731
772
  for await (const msg of client.fetch(uidList, {
732
773
  envelope: true,
733
774
  uid: true,
734
775
  bodyStructure: true,
735
- bodyParts: [{ key: "1", start: 0, maxLength: BODY_MAX_BYTES }],
776
+ source: true,
736
777
  }, { uid: true })) {
737
778
  // Track highest UID for high-water mark
738
779
  const uid = Number(msg.uid);
@@ -754,9 +795,18 @@ export async function fetchSinceUid(config, password, sinceUid, recipients) {
754
795
  const inReplyTo = msg.envelope?.inReplyTo ?? null;
755
796
  const toAddresses = (msg.envelope?.to ?? []).map((a) => a.address).filter((a) => !!a);
756
797
  const ccAddresses = (msg.envelope?.cc ?? []).map((a) => a.address).filter((a) => !!a);
757
- const bodyPreview = extractBodyFromFetch(msg);
758
798
  const attachments = walkAttachmentMetadata(msg.bodyStructure);
759
- emails.push({ uid, messageId, inReplyTo, toAddresses, ccAddresses, fromAddress: fromAddr, fromName, subject, date, bodyPreview, attachments });
799
+ bodySources.push({
800
+ source: msg.source ?? Buffer.alloc(0),
801
+ structureType: msg.bodyStructure?.type ?? "?",
802
+ });
803
+ emails.push({ uid, messageId, inReplyTo, toAddresses, ccAddresses, fromAddress: fromAddr, fromName, subject, date, bodyPreview: "", attachments });
804
+ }
805
+ // Parse the captured sources now that the fetch stream is closed.
806
+ for (let i = 0; i < emails.length; i++) {
807
+ const { body, kind } = await extractBody(bodySources[i].source);
808
+ emails[i].bodyPreview = previewBody(body);
809
+ diag("fetchSinceUid", `body #${i + 1}`, `uid=${emails[i].uid} preview=${emails[i].bodyPreview.length}chars type=${bodySources[i].structureType} bodySource=${kind}`);
760
810
  }
761
811
  diag("fetchSinceUid", "fetch done", `${emails.length} emails (maxUid=${maxUid}) in ${Date.now() - t2}ms (total ${Date.now() - t0}ms)`);
762
812
  if (recipients) {
@@ -843,6 +893,70 @@ export async function fetchEnvelopeByMessageId(config, password, messageId) {
843
893
  await safeLogout(client);
844
894
  }
845
895
  }
896
+ /**
897
+ * Fetch the COMPLETE decoded body of one message, addressed by UID (in a
898
+ * folder) or by Message-ID (resolved across INBOX and \Sent). Downloads the
899
+ * full RFC822 source and decodes it with mailparser — no 500-char cap and no
900
+ * MIME-shape blind spot. Returns null when the message cannot be located.
901
+ */
902
+ export async function fetchFullBody(config, password, target) {
903
+ let mailbox = "";
904
+ let uid;
905
+ if ("messageId" in target) {
906
+ const env = await fetchEnvelopeByMessageId(config, password, target.messageId);
907
+ if (!env)
908
+ return null;
909
+ mailbox = env.mailbox;
910
+ uid = env.uid;
911
+ }
912
+ else {
913
+ uid = target.uid;
914
+ }
915
+ const client = createClient(config, password);
916
+ const t0 = Date.now();
917
+ const label = "messageId" in target
918
+ ? `messageId=${target.messageId}`
919
+ : `uid=${target.uid} folder=${target.folder ?? "inbox"}`;
920
+ diag("fetchFullBody", "start", label);
921
+ try {
922
+ await client.connect();
923
+ if (!("messageId" in target)) {
924
+ mailbox = await resolveFolderName(client, target.folder ?? "inbox");
925
+ }
926
+ const lock = await client.getMailboxLock(mailbox);
927
+ try {
928
+ const msg = await client.fetchOne(String(uid), { uid: true, source: true, envelope: true }, { uid: true });
929
+ if (!msg || !msg.source) {
930
+ diag("fetchFullBody", "not-found", `uid=${uid} mailbox=${mailbox} in ${Date.now() - t0}ms`);
931
+ return null;
932
+ }
933
+ const src = msg.source;
934
+ const { body, kind } = await extractBody(src);
935
+ const env = msg.envelope ?? {};
936
+ diag("fetchFullBody", "done", `uid=${uid} mailbox=${mailbox} bytes=${src.length} bodyLen=${body.length} bodySource=${kind} in ${Date.now() - t0}ms`);
937
+ return {
938
+ uid: msg.uid,
939
+ messageId: env.messageId ?? null,
940
+ from: env.from?.[0]?.address ?? "(unknown)",
941
+ subject: env.subject ?? "(no subject)",
942
+ date: env.date?.toISOString() ?? "(unknown date)",
943
+ body,
944
+ bodySource: kind,
945
+ bytes: src.length,
946
+ };
947
+ }
948
+ finally {
949
+ lock.release();
950
+ }
951
+ }
952
+ catch (err) {
953
+ diag("fetchFullBody", "FAILED", `${err instanceof Error ? err.message : String(err)} after ${Date.now() - t0}ms`);
954
+ throw err;
955
+ }
956
+ finally {
957
+ await safeLogout(client);
958
+ }
959
+ }
846
960
  /**
847
961
  * Get the inbox message count.
848
962
  */
@@ -944,33 +1058,66 @@ export async function fetchDraftSource(config, password, uid) {
944
1058
  }
945
1059
  }
946
1060
  /**
947
- * Read-modify-replace cycle for an existing draft addressed by Drafts-folder UID.
1061
+ * Read-modify-replace cycle for an existing draft. The recorded Drafts UID is a
1062
+ * fast-path hint; the draft's own `Message-ID` is the stable identity, because
1063
+ * Gmail renumbers a draft's UID when it is touched or re-synced.
1064
+ *
1065
+ * Resolution order, inside the Drafts lock: the recorded UID if still live, else
1066
+ * a `SEARCH HEADER Message-ID` relocate (when a draftMessageId is supplied),
1067
+ * else nothing. In every path the replacement is APPENDed (with the \Draft flag)
1068
+ * BEFORE the resolved old copy is expunged, so a mid-cycle failure never leaves
1069
+ * zero copies.
948
1070
  *
949
- * Atomicity contract: the replacement is APPENDed (with the \Draft flag) BEFORE
950
- * the superseded UID is expunged, so a mid-cycle failure never leaves zero
951
- * copies. The old UID's existence is confirmed first; an absent UID throws
952
- * before any append, so a bad UID never produces an orphan replacement.
953
- * `newUid` is the APPENDUID when the server supports UIDPLUS, else null.
1071
+ * When neither identity resolves a live draft, the prior draft is genuinely
1072
+ * gone: the replacement is appended as a fresh draft and `created:true` is
1073
+ * returned no throw. `newUid` is the APPENDUID when the server supports
1074
+ * UIDPLUS, else null.
1075
+ *
1076
+ * A single transient transport drop is absorbed by the bounded retry; permanent
1077
+ * errors (auth) surface immediately.
954
1078
  */
955
- export async function replaceDraft(config, password, oldUid, newRaw) {
1079
+ export async function replaceDraft(config, password, oldUid, newRaw, draftMessageId) {
1080
+ return withTransientRetry("draftReplace", () => replaceDraftOnce(config, password, oldUid, newRaw, draftMessageId ?? null));
1081
+ }
1082
+ async function replaceDraftOnce(config, password, oldUid, newRaw, draftMessageId) {
956
1083
  const client = createClient(config, password);
957
1084
  const t0 = Date.now();
958
- diag("draftReplace", "start", `oldUid=${oldUid} bytes=${newRaw.length}`);
1085
+ diag("draftReplace", "start", `oldUid=${oldUid} bytes=${newRaw.length} draftMessageId=${draftMessageId ?? "none"}`);
959
1086
  try {
960
1087
  await client.connect();
961
1088
  const folder = await resolveFolderName(client, "drafts");
962
1089
  const lock = await client.getMailboxLock(folder);
963
1090
  try {
1091
+ // Resolve the live UID: fast-path the recorded UID, else relocate by the
1092
+ // draft's own Message-ID, else accept that the prior draft is gone.
1093
+ let resolvedUid = null;
1094
+ let addressing = "created-fresh";
964
1095
  const existing = await client.fetchOne(String(oldUid), { uid: true }, { uid: true });
965
- if (!existing) {
966
- throw new Error(`Draft UID ${oldUid} not found in ${folder}.`);
1096
+ if (existing) {
1097
+ resolvedUid = oldUid;
1098
+ addressing = "uid-hit";
967
1099
  }
1100
+ else if (draftMessageId) {
1101
+ const needle = `<${draftMessageId.replace(/^<|>$/g, "")}>`;
1102
+ const uids = await client.search({ header: { "message-id": needle } }, { uid: true });
1103
+ if (uids && uids.length > 0) {
1104
+ resolvedUid = uids[0];
1105
+ addressing = "message-id-relocate";
1106
+ }
1107
+ }
1108
+ // Append the replacement first in every path — the atomicity invariant.
968
1109
  const appended = await client.append(folder, newRaw, ["\\Draft"]);
969
1110
  const newUid = appended && typeof appended.uid === "number" ? appended.uid : null;
970
- diag("draftReplace", "appended", `folder=${folder} newUid=${newUid ?? "not-reported"}`);
971
- const expunged = await client.messageDelete(String(oldUid), { uid: true });
972
- diag("draftReplace", "expunged", `oldUid=${oldUid} newUid=${newUid ?? "not-reported"} expunge=${expunged} in ${Date.now() - t0}ms`);
973
- return { folder, oldUid, newUid, expunged: !!expunged };
1111
+ if (resolvedUid === null) {
1112
+ // Create-on-absent recovery: distinct marker so it is not read as a
1113
+ // normal replace. Nothing to expunge.
1114
+ diag("draftReplace", "created-fresh", `folder=${folder} newUid=${newUid ?? "not-reported"} oldUid=${oldUid} draftMessageId=${draftMessageId ?? "none"} in ${Date.now() - t0}ms`);
1115
+ return { folder, oldUid, newUid, expunged: false, created: true, addressing };
1116
+ }
1117
+ diag("draftReplace", "appended", `folder=${folder} newUid=${newUid ?? "not-reported"} addressing=${addressing} resolvedUid=${resolvedUid}`);
1118
+ const expunged = await client.messageDelete(String(resolvedUid), { uid: true });
1119
+ diag("draftReplace", "expunged", `resolvedUid=${resolvedUid} newUid=${newUid ?? "not-reported"} addressing=${addressing} expunge=${expunged} in ${Date.now() - t0}ms`);
1120
+ return { folder, oldUid, newUid, expunged: !!expunged, created: false, addressing };
974
1121
  }
975
1122
  finally {
976
1123
  lock.release();
@@ -986,10 +1133,18 @@ export async function replaceDraft(config, password, oldUid, newRaw) {
986
1133
  }
987
1134
  /**
988
1135
  * Delete a stored draft addressed by Drafts-folder UID (used by the send path
989
- * after SMTP accepts the message). The UID's existence is confirmed first; an
990
- * absent UID throws rather than silently no-op'ing.
1136
+ * after SMTP accepts the message). An absent UID is treated as success with
1137
+ * nothing removed (`deleted:false`) deletion's goal is already met, so a
1138
+ * stale UID is not an error. A single transient transport drop is absorbed by
1139
+ * the bounded retry.
1140
+ *
1141
+ * Message-ID relocation is deliberately not applied here: the send path
1142
+ * addresses by UID only and carries no draft Message-ID to relocate by.
991
1143
  */
992
1144
  export async function deleteDraft(config, password, uid) {
1145
+ return withTransientRetry("draftDelete", () => deleteDraftOnce(config, password, uid));
1146
+ }
1147
+ async function deleteDraftOnce(config, password, uid) {
993
1148
  const client = createClient(config, password);
994
1149
  const t0 = Date.now();
995
1150
  diag("draftDelete", "start", `uid=${uid}`);
@@ -1000,7 +1155,8 @@ export async function deleteDraft(config, password, uid) {
1000
1155
  try {
1001
1156
  const existing = await client.fetchOne(String(uid), { uid: true }, { uid: true });
1002
1157
  if (!existing) {
1003
- throw new Error(`Draft UID ${uid} not found in ${folder}.`);
1158
+ diag("draftDelete", "absent", `uid=${uid} folder=${folder} in ${Date.now() - t0}ms`);
1159
+ return { folder, deleted: false };
1004
1160
  }
1005
1161
  const deleted = await client.messageDelete(String(uid), { uid: true });
1006
1162
  diag("draftDelete", "deleted", `uid=${uid} folder=${folder} deleted=${deleted} in ${Date.now() - t0}ms`);