@rubytech/create-maxy-code 0.1.357 → 0.1.359

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 (260) hide show
  1. package/dist/__tests__/claude-ptys-slice.test.js +36 -1
  2. package/dist/__tests__/neo4j-pidfile-scrub.test.js +103 -0
  3. package/dist/index.js +42 -7
  4. package/dist/neo4j-pidfile-scrub.js +90 -0
  5. package/dist/port-resolution.js +18 -1
  6. package/package.json +1 -1
  7. package/payload/platform/package-lock.json +237 -0
  8. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js +79 -1
  9. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js.map +1 -1
  10. package/payload/platform/plugins/admin/mcp/dist/index.js +17 -3
  11. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  12. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts +16 -0
  13. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts.map +1 -1
  14. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js +52 -0
  15. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js.map +1 -1
  16. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +61 -1
  17. package/payload/platform/plugins/cloudflare/PLUGIN.md +7 -1
  18. package/payload/platform/plugins/cloudflare/bin/__tests__/cf-token.test.sh +195 -0
  19. package/payload/platform/plugins/cloudflare/bin/cf-token.sh +157 -0
  20. package/payload/platform/plugins/cloudflare/references/api.md +2 -0
  21. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +14 -0
  22. package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +10 -1
  23. package/payload/platform/plugins/docs/PLUGIN.md +2 -0
  24. package/payload/platform/plugins/docs/references/admin-ui.md +2 -0
  25. package/payload/platform/plugins/docs/references/claudeai-connectors.md +45 -0
  26. package/payload/platform/plugins/docs/references/cloudflare.md +6 -0
  27. package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
  28. package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
  29. package/payload/platform/plugins/email/PLUGIN.md +7 -3
  30. package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.d.ts +2 -0
  31. package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.js +99 -0
  33. package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.js.map +1 -0
  34. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.d.ts +2 -0
  35. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.d.ts.map +1 -0
  36. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.js +32 -0
  37. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.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-status.test.d.ts +2 -0
  43. package/payload/platform/plugins/email/mcp/dist/__tests__/email-status.test.d.ts.map +1 -0
  44. package/payload/platform/plugins/email/mcp/dist/__tests__/email-status.test.js +58 -0
  45. package/payload/platform/plugins/email/mcp/dist/__tests__/email-status.test.js.map +1 -0
  46. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +94 -9
  47. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  48. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-probe.test.js +109 -1
  49. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-probe.test.js.map +1 -1
  50. package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.js +4 -0
  51. package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.js.map +1 -1
  52. package/payload/platform/plugins/email/mcp/dist/index.js +39 -2
  53. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  54. package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts +5 -0
  55. package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts.map +1 -1
  56. package/payload/platform/plugins/email/mcp/dist/lib/compose.js +8 -1
  57. package/payload/platform/plugins/email/mcp/dist/lib/compose.js.map +1 -1
  58. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts +13 -0
  59. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -1
  60. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +35 -0
  61. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -1
  62. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +83 -30
  63. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  64. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +327 -112
  65. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  66. package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.d.ts +19 -0
  67. package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.d.ts.map +1 -1
  68. package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.js +33 -0
  69. package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.js.map +1 -1
  70. package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts +6 -0
  71. package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts.map +1 -1
  72. package/payload/platform/plugins/email/mcp/dist/lib/smtp.js +4 -0
  73. package/payload/platform/plugins/email/mcp/dist/lib/smtp.js.map +1 -1
  74. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.d.ts +8 -1
  75. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.d.ts.map +1 -1
  76. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.js +10 -6
  77. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.js.map +1 -1
  78. package/payload/platform/plugins/email/mcp/dist/tools/email-draft.d.ts.map +1 -1
  79. package/payload/platform/plugins/email/mcp/dist/tools/email-draft.js +4 -2
  80. package/payload/platform/plugins/email/mcp/dist/tools/email-draft.js.map +1 -1
  81. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.d.ts +15 -0
  82. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.d.ts.map +1 -0
  83. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.js +29 -0
  84. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.js.map +1 -0
  85. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts.map +1 -1
  86. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +11 -1
  87. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
  88. package/payload/platform/plugins/email/mcp/dist/tools/email-status.d.ts.map +1 -1
  89. package/payload/platform/plugins/email/mcp/dist/tools/email-status.js +24 -1
  90. package/payload/platform/plugins/email/mcp/dist/tools/email-status.js.map +1 -1
  91. package/payload/platform/plugins/email/mcp/package.json +2 -0
  92. package/payload/platform/plugins/email/references/email-reference.md +30 -3
  93. package/payload/platform/scripts/rss-sampler.sh +21 -0
  94. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  95. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  96. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  97. package/payload/platform/services/claude-session-manager/dist/connector-auth.d.ts +31 -0
  98. package/payload/platform/services/claude-session-manager/dist/connector-auth.d.ts.map +1 -0
  99. package/payload/platform/services/claude-session-manager/dist/connector-auth.js +49 -0
  100. package/payload/platform/services/claude-session-manager/dist/connector-auth.js.map +1 -0
  101. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +24 -0
  102. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  103. package/payload/platform/services/claude-session-manager/dist/http-server.js +214 -17
  104. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  105. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +13 -5
  106. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  107. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +46 -11
  108. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  109. package/payload/platform/services/webchat-channel/dist/instructions.d.ts +1 -0
  110. package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
  111. package/payload/platform/services/webchat-channel/dist/instructions.js +28 -0
  112. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  113. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  114. package/payload/platform/services/webchat-channel/dist/server.js +75 -5
  115. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  116. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  117. package/payload/server/public/assets/{AdminLoginScreens-BW1UgfoI.js → AdminLoginScreens-tHyAPNnQ.js} +1 -1
  118. package/payload/server/public/assets/AdminShell-Ce9izNVZ.js +1 -0
  119. package/payload/server/public/assets/{Checkbox-O1qvMk_u.js → Checkbox-Cq6SJJlX.js} +1 -1
  120. package/payload/server/public/assets/OperatorConversations-BERW_hUa.css +1 -0
  121. package/payload/server/public/assets/OperatorConversations-m2lz6Ss7.js +9 -0
  122. package/payload/server/public/assets/admin-D9iMgdbN.js +1 -0
  123. package/payload/server/public/assets/{arc-DpkSQNto.js → arc-Crkz45LN.js} +1 -1
  124. package/payload/server/public/assets/architecture-YZFGNWBL-Dma4Bgha.js +1 -0
  125. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Bw7Yq8O9.js → architectureDiagram-Q4EWVU46-C9JThHgP.js} +1 -1
  126. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BBtJQFSz.js → blockDiagram-DXYQGD6D-DpsV_HXT.js} +1 -1
  127. package/payload/server/public/assets/{browser-CySvj140.js → browser-CZWi0EAP.js} +1 -1
  128. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-JAMgxzOr.js → c4Diagram-AHTNJAMY-CTm_6aKi.js} +1 -1
  129. package/payload/server/public/assets/calendar-DUAh-_kx.js +1 -0
  130. package/payload/server/public/assets/channel-B_-5bau1.js +1 -0
  131. package/payload/server/public/assets/chat-DHuibPfc.js +1 -0
  132. package/payload/server/public/assets/{chunk-2KRD3SAO-eDugsc2L.js → chunk-2KRD3SAO-1KSJW1q6.js} +1 -1
  133. package/payload/server/public/assets/{chunk-336JU56O-BXfECwdC.js → chunk-336JU56O-C3t08Ip-.js} +2 -2
  134. package/payload/server/public/assets/chunk-426QAEUC-Dirxdwjh.js +1 -0
  135. package/payload/server/public/assets/{chunk-4BX2VUAB-CByVeKCY.js → chunk-4BX2VUAB-kl1ze33n.js} +1 -1
  136. package/payload/server/public/assets/{chunk-4TB4RGXK-CpY0DcUB.js → chunk-4TB4RGXK-DxpoDoHg.js} +1 -1
  137. package/payload/server/public/assets/{chunk-55IACEB6-C3YxgyIb.js → chunk-55IACEB6-WM9VJzha.js} +1 -1
  138. package/payload/server/public/assets/{chunk-5FUZZQ4R-Bc0d7j8A.js → chunk-5FUZZQ4R-CLH63ZH3.js} +1 -1
  139. package/payload/server/public/assets/{chunk-5PVQY5BW-CTUGMRIa.js → chunk-5PVQY5BW-DCTb8IAT.js} +1 -1
  140. package/payload/server/public/assets/{chunk-67CJDMHE-CbdPnpLd.js → chunk-67CJDMHE-BSYzyaoH.js} +1 -1
  141. package/payload/server/public/assets/{chunk-7N4EOEYR-Cjl2kevz.js → chunk-7N4EOEYR-CarJ7O2P.js} +1 -1
  142. package/payload/server/public/assets/{chunk-AA7GKIK3-BgozIh5p.js → chunk-AA7GKIK3-DTNLyBpH.js} +1 -1
  143. package/payload/server/public/assets/{chunk-BSJP7CBP-6Ufq45NW.js → chunk-BSJP7CBP-BFmHflHY.js} +1 -1
  144. package/payload/server/public/assets/{chunk-CIAEETIT-BhhYuMnA.js → chunk-CIAEETIT-BpjG0IzU.js} +1 -1
  145. package/payload/server/public/assets/{chunk-EDXVE4YY-BfaJ3yDD.js → chunk-EDXVE4YY-2fSowhlW.js} +1 -1
  146. package/payload/server/public/assets/{chunk-ENJZ2VHE-C7-hIGVC.js → chunk-ENJZ2VHE-ClcranGV.js} +1 -1
  147. package/payload/server/public/assets/{chunk-FMBD7UC4-BQvvyocF.js → chunk-FMBD7UC4-DLq4BItQ.js} +1 -1
  148. package/payload/server/public/assets/{chunk-FOC6F5B3-C6rBDOxH.js → chunk-FOC6F5B3-B9QI3Guz.js} +1 -1
  149. package/payload/server/public/assets/{chunk-ICPOFSXX-BjOJvkVa.js → chunk-ICPOFSXX-BAr-Mv1Z.js} +2 -2
  150. package/payload/server/public/assets/{chunk-K5T4RW27-kKowRHhn.js → chunk-K5T4RW27-8PvAHasY.js} +1 -1
  151. package/payload/server/public/assets/{chunk-KGLVRYIC-bAEV_5eH.js → chunk-KGLVRYIC--aGaDRH5.js} +1 -1
  152. package/payload/server/public/assets/{chunk-LIHQZDEY-BLvsQAfg.js → chunk-LIHQZDEY-BrWj7mw2.js} +1 -1
  153. package/payload/server/public/assets/{chunk-ORNJ4GCN-BzYZfPZ7.js → chunk-ORNJ4GCN-kYQmkMTi.js} +1 -1
  154. package/payload/server/public/assets/{chunk-OYMX7WX6-CL7WRw42.js → chunk-OYMX7WX6-CIsryCWH.js} +1 -1
  155. package/payload/server/public/assets/chunk-QZHKN3VN-Duipo1kI.js +1 -0
  156. package/payload/server/public/assets/{chunk-U2HBQHQK-BdcVGjau.js → chunk-U2HBQHQK-68X653e3.js} +1 -1
  157. package/payload/server/public/assets/{chunk-X2U36JSP-C0CL_Jin.js → chunk-X2U36JSP-v3v9soX3.js} +1 -1
  158. package/payload/server/public/assets/{chunk-XPW4576I-CsV7CUxH.js → chunk-XPW4576I-CW5DPpop.js} +1 -1
  159. package/payload/server/public/assets/{chunk-YZCP3GAM-Cj0_fLkR.js → chunk-YZCP3GAM-B1VP7Rl2.js} +1 -1
  160. package/payload/server/public/assets/{chunk-ZZ45TVLE-DLuquyoY.js → chunk-ZZ45TVLE-DiJrujNY.js} +1 -1
  161. package/payload/server/public/assets/classDiagram-6PBFFD2Q-B3kX5SuB.js +1 -0
  162. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-D761c7Lr.js +1 -0
  163. package/payload/server/public/assets/clone-BrhFv6Tb.js +1 -0
  164. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-BU3EOLXX.js → cose-bilkent-S5V4N54A-BscXxLMc.js} +1 -1
  165. package/payload/server/public/assets/{dagre-KV5264BT-CoCAYhFc.js → dagre-KV5264BT-DVgGlTQ1.js} +1 -1
  166. package/payload/server/public/assets/{dagre-BRq8C9NU.js → dagre-MBv6WlCS.js} +1 -1
  167. package/payload/server/public/assets/data-CoOcAR_2.js +1 -0
  168. package/payload/server/public/assets/{diagram-5BDNPKRD-DBy5GNaQ.js → diagram-5BDNPKRD-BmhCaO0a.js} +1 -1
  169. package/payload/server/public/assets/{diagram-G4DWMVQ6-l93MsBLv.js → diagram-G4DWMVQ6-ChU9L8vz.js} +1 -1
  170. package/payload/server/public/assets/{diagram-MMDJMWI5-BIfXkVlL.js → diagram-MMDJMWI5-Oy409Zc_.js} +1 -1
  171. package/payload/server/public/assets/{diagram-TYMM5635-BuTJLBny.js → diagram-TYMM5635-Cn1YByqk.js} +1 -1
  172. package/payload/server/public/assets/{erDiagram-SMLLAGMA-BwPxWDbP.js → erDiagram-SMLLAGMA-e17yfLvr.js} +1 -1
  173. package/payload/server/public/assets/{flatten-DtM0XAMt.js → flatten-Cl1Bc3dR.js} +1 -1
  174. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BhPtHXMR.js → flowDiagram-DWJPFMVM-Dqt7sCVZ.js} +1 -1
  175. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BlXjprdD.js → ganttDiagram-T4ZO3ILL-B4IhwS2r.js} +1 -1
  176. package/payload/server/public/assets/gitGraph-7Q5UKJZL-BCQAqtLy.js +1 -0
  177. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BW36BDiu.js → gitGraphDiagram-UUTBAWPF-BDw-PKcI.js} +1 -1
  178. package/payload/server/public/assets/{graph-uFhGyOZR.js → graph-Dc_votDd.js} +3 -3
  179. package/payload/server/public/assets/{graph-labels-BNnhRQqM.js → graph-labels-jqagxwhW.js} +1 -1
  180. package/payload/server/public/assets/{graphlib-Bjg3Tq-k.js → graphlib-DPIYk_sg.js} +1 -1
  181. package/payload/server/public/assets/info-OMHHGYJF-qU7hK50T.js +1 -0
  182. package/payload/server/public/assets/infoDiagram-42DDH7IO-KGdcJeTs.js +2 -0
  183. package/payload/server/public/assets/{isEmpty-D__iH1WQ.js → isEmpty-D1pGOD1J.js} +1 -1
  184. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CmB-bA_O.js → ishikawaDiagram-UXIWVN3A-BRIHsB3G.js} +1 -1
  185. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-Bt1fJKmc.js → journeyDiagram-VCZTEJTY-aKNgmoHd.js} +1 -1
  186. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DVbksz_J.js → kanban-definition-6JOO6SKY-cwBwT-nm.js} +1 -1
  187. package/payload/server/public/assets/{line-BX2Ugx3B.js → line-DVtVRzUA.js} +1 -1
  188. package/payload/server/public/assets/{linear-Bdc-QJWI.js → linear-DtoOFPO2.js} +1 -1
  189. package/payload/server/public/assets/{mermaid-parser.core-qv1e-J5x.js → mermaid-parser.core-G7YD_zO3.js} +2 -2
  190. package/payload/server/public/assets/{mermaid.core-C_d9YQX_.js → mermaid.core-DpFyndHm.js} +3 -3
  191. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DwKGvF14.js → mindmap-definition-QFDTVHPH-CYXK_Pvz.js} +1 -1
  192. package/payload/server/public/assets/operator-KYXcuQ4Y.js +1 -0
  193. package/payload/server/public/assets/{ordinal-BRQ5CzZY.js → ordinal-CBZeH4Yp.js} +1 -1
  194. package/payload/server/public/assets/packet-4T2RLAQJ-DiU2yS9m.js +1 -0
  195. package/payload/server/public/assets/page-Cfsv97nq.js +32 -0
  196. package/payload/server/public/assets/pie-ZZUOXDRM-zHUKKlVv.js +1 -0
  197. package/payload/server/public/assets/{pieDiagram-DEJITSTG-dUvQC_4S.js → pieDiagram-DEJITSTG-DFo4N6WH.js} +1 -1
  198. package/payload/server/public/assets/{public-DgltXhnD.js → public-49guEjCh.js} +1 -1
  199. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CebX_oPa.js → quadrantDiagram-34T5L4WZ-BlH5iIhl.js} +1 -1
  200. package/payload/server/public/assets/radar-PYXPWWZC-BUnVoaPg.js +1 -0
  201. package/payload/server/public/assets/{reduce-CkcXOmmJ.js → reduce-PuPlFPRX.js} +1 -1
  202. package/payload/server/public/assets/{requirementDiagram-MS252O5E-h2euIqGj.js → requirementDiagram-MS252O5E-D5WLty8A.js} +1 -1
  203. package/payload/server/public/assets/rotate-ccw-CyR5o3Nq.js +1 -0
  204. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DCii3fkF.js → sankeyDiagram-XADWPNL6-vB85IxHG.js} +1 -1
  205. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-CQSGYmlq.js → sequenceDiagram-FGHM5R23-Cxraa0Ee.js} +1 -1
  206. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-CVtU0iXH.js → stateDiagram-FHFEXIEX-BBPssYBx.js} +1 -1
  207. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BWmmUwDt.js +1 -0
  208. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-ChvsBltI.js → timeline-definition-GMOUNBTQ-Bgk6PT62.js} +1 -1
  209. package/payload/server/public/assets/treeView-SZITEDCU-C3Y-Q52h.js +1 -0
  210. package/payload/server/public/assets/treemap-W4RFUUIX-Dk0qZpAg.js +1 -0
  211. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-e-EZiiba.js → vennDiagram-DHZGUBPP-B-APTs3L.js} +1 -1
  212. package/payload/server/public/assets/wardley-RL74JXVD-BAWI02OA.js +1 -0
  213. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-4JaRn1s5.js → wardleyDiagram-NUSXRM2D-CkXJSs_i.js} +1 -1
  214. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-kxwmCNuM.js → xychartDiagram-5P7HB3ND-BkyshPMV.js} +1 -1
  215. package/payload/server/public/browser.html +4 -4
  216. package/payload/server/public/calendar.html +4 -4
  217. package/payload/server/public/chat.html +7 -6
  218. package/payload/server/public/data.html +4 -4
  219. package/payload/server/public/graph.html +7 -6
  220. package/payload/server/public/index.html +6 -6
  221. package/payload/server/public/operator.html +9 -8
  222. package/payload/server/public/public.html +7 -6
  223. package/payload/server/server.js +244 -24
  224. package/payload/server/public/assets/AdminShell-CDQDkT1R.js +0 -1
  225. package/payload/server/public/assets/OperatorConversations-CVH-FoJo.css +0 -1
  226. package/payload/server/public/assets/OperatorConversations-jqaPCHwB.js +0 -9
  227. package/payload/server/public/assets/admin-BkYc54bi.js +0 -1
  228. package/payload/server/public/assets/architecture-YZFGNWBL-Csj53u5T.js +0 -1
  229. package/payload/server/public/assets/calendar-DgP7lal2.js +0 -1
  230. package/payload/server/public/assets/channel-BUQUAUts.js +0 -1
  231. package/payload/server/public/assets/chat-gls5aV50.js +0 -1
  232. package/payload/server/public/assets/chunk-426QAEUC-CdknReB2.js +0 -1
  233. package/payload/server/public/assets/chunk-QZHKN3VN-8tHoEDE9.js +0 -1
  234. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BEGZ3DLg.js +0 -1
  235. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DsLxnm5U.js +0 -1
  236. package/payload/server/public/assets/clone-uiXbH_kh.js +0 -1
  237. package/payload/server/public/assets/data-CbI8-CX3.js +0 -1
  238. package/payload/server/public/assets/gitGraph-7Q5UKJZL-D_2KrZoP.js +0 -1
  239. package/payload/server/public/assets/info-OMHHGYJF-BP2TNwQ4.js +0 -1
  240. package/payload/server/public/assets/infoDiagram-42DDH7IO-D1kDtBOY.js +0 -2
  241. package/payload/server/public/assets/operator-DeCk_qYw.js +0 -1
  242. package/payload/server/public/assets/packet-4T2RLAQJ-BDg8fde8.js +0 -1
  243. package/payload/server/public/assets/page-jGwq-DpW.js +0 -32
  244. package/payload/server/public/assets/pie-ZZUOXDRM-CXDyYMkZ.js +0 -1
  245. package/payload/server/public/assets/radar-PYXPWWZC-xen_zv8F.js +0 -1
  246. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BywcXjtb.js +0 -1
  247. package/payload/server/public/assets/treeView-SZITEDCU-2FBf_1It.js +0 -1
  248. package/payload/server/public/assets/treemap-W4RFUUIX-BDdb30P-.js +0 -1
  249. package/payload/server/public/assets/wardley-RL74JXVD-BLq5MxjR.js +0 -1
  250. /package/payload/server/public/assets/{_baseFor-SRtUHe7G.js → _baseFor-brRjpUOX.js} +0 -0
  251. /package/payload/server/public/assets/{array-BpsnM-Py.js → array-BGFCBI0e.js} +0 -0
  252. /package/payload/server/public/assets/{cytoscape.esm-D4Rl1H5h.js → cytoscape.esm-TvRJ2tGX.js} +0 -0
  253. /package/payload/server/public/assets/{defaultLocale-C7sQPl-O.js → defaultLocale-CRZydyG6.js} +0 -0
  254. /package/payload/server/public/assets/{dist-CtUNt3w8.js → dist-B0CkUodR.js} +0 -0
  255. /package/payload/server/public/assets/{init-BydxaDEV.js → init-B8gtcn7T.js} +0 -0
  256. /package/payload/server/public/assets/{katex-B0fVeDx4.js → katex-RxgSu_dy.js} +0 -0
  257. /package/payload/server/public/assets/{path-jlWYQ2i9.js → path-DZF-JdEe.js} +0 -0
  258. /package/payload/server/public/assets/{preload-helper-uTix4PVD.js → preload-helper-CQ36nxok.js} +0 -0
  259. /package/payload/server/public/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
  260. /package/payload/server/public/assets/{src-48kCpScq.js → src-CR_MU8uy.js} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { ImapFlow } from "imapflow";
2
- import { trackProbe, releaseProbe, newProbeToken } from "./setup-probe.js";
2
+ import { simpleParser } from "mailparser";
3
+ import { trackProbe, releaseProbe, newProbeToken, containProbeSocket } from "./setup-probe.js";
3
4
  const CONNECT_TIMEOUT = 10_000;
4
5
  const COMMAND_TIMEOUT = 30_000;
5
6
  /** Diagnostic logging to stderr (captured by claude-agent-stderr log on Pi) */
@@ -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;
@@ -221,105 +300,99 @@ export async function fetchAttachmentBytesForMessages(config, password, requests
221
300
  return results;
222
301
  }
223
302
  /**
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.
303
+ * Classify an IMAP probe error by its structured fields, falling back to message
304
+ * strings only where no structured field carries the signal.
246
305
  *
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.
306
+ * The auth case is why this exists: ImapFlow reports an authentication rejection
307
+ * as a generic `Error("Command failed")` and carries the real signal in
308
+ * `authenticationFailed` / `serverResponseCode` / `responseText` (set by
309
+ * imapflow's login.js / authenticate.js), none of which appear in `err.message`.
310
+ * The transport cases (refused/not-found/timeout) carry `err.code` from Node's
311
+ * socket layer; TLS errors carry a cert/TLS/SSL marker in code or message.
251
312
  */
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;
313
+ export function classifyImapError(err) {
314
+ const e = (err ?? {});
315
+ const message = typeof e.message === "string" ? e.message : String(err);
316
+ const code = typeof e.code === "string" ? e.code : "";
317
+ const serverCode = typeof e.serverResponseCode === "string" ? e.serverResponseCode : "";
318
+ const responseText = typeof e.responseText === "string" ? e.responseText : "";
319
+ // Order: structured/code signals beat fuzzy message strings. Auth's structured
320
+ // fields are unambiguous and come first. The code-bearing transport cases are
321
+ // checked before the message-only TLS and auth fallbacks, so a timeout/refusal
322
+ // whose OS message embeds a hostname containing "ssl"/"tls"/"authentication"
323
+ // is not stolen by a later substring match.
324
+ if (e.authenticationFailed === true || serverCode === "AUTHENTICATIONFAILED")
325
+ return "auth";
326
+ if (code === "ECONNREFUSED" || message.includes("ECONNREFUSED"))
327
+ return "refused";
328
+ if (code === "ENOTFOUND" || message.includes("ENOTFOUND"))
329
+ return "not-found";
330
+ if (code === "ETIMEDOUT" || code === "CONNECT_TIMEOUT" || code === "GREETING_TIMEOUT")
331
+ return "timeout";
332
+ // TLS errors carry a cert/TLS/SSL marker in the code (e.g. CERT_HAS_EXPIRED,
333
+ // ERR_TLS_*) or the message; checked on code first, message second.
334
+ if (/cert|TLS|SSL/i.test(`${code} ${message}`))
335
+ return "tls";
336
+ // Message-only fallbacks for errors that arrived without a structured field —
337
+ // last resort, after every code-based case above.
338
+ if (/AUTHENTICATIONFAILED|Invalid credentials|LOGIN failed|Authentication failed/i.test(`${message} ${responseText}`))
339
+ return "auth";
340
+ if (/ETIMEDOUT|timed?\s?out/i.test(message))
341
+ return "timeout";
342
+ return "unknown";
343
+ }
344
+ /** Map a classified failure to the operator-actionable message. */
345
+ function imapFailureMessage(kind, config, err) {
346
+ switch (kind) {
347
+ case "auth":
348
+ return `IMAP authentication failed for ${config.email}. Check the password — most providers require an app-specific password when 2FA is enabled.`;
349
+ case "refused":
350
+ return `IMAP connection refused at ${config.imapHost}:${config.imapPort}. Check that IMAP is enabled in your email provider settings.`;
351
+ case "not-found":
352
+ return `IMAP host ${config.imapHost} not found. Check the hostname.`;
353
+ case "tls":
354
+ return `IMAP TLS error connecting to ${config.imapHost}:${config.imapPort}. Try port 993 (TLS) or 143 (STARTTLS).`;
355
+ case "timeout":
356
+ return `IMAP connection timed out to ${config.imapHost}:${config.imapPort}. Check the host and port.`;
357
+ default:
358
+ return `IMAP connection failed: ${err instanceof Error ? err.message : String(err)}`;
268
359
  }
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
360
  }
287
361
  /**
288
362
  * Test IMAP connectivity and authentication.
289
363
  * Returns null on success, error message on failure.
364
+ *
365
+ * Emits one structured attempt line per probe (host/port/security/outcome/reason,
366
+ * no secret) so a failing setup is visible server-side without opening the session
367
+ * transcript.
290
368
  */
291
369
  export async function testConnection(config, password, token = newProbeToken()) {
292
370
  const client = createClient(config, password);
293
371
  trackProbe("imap", client, token);
372
+ const attempt = (outcome, reason) => {
373
+ process.stderr.write(`[email] imap-attempt host=${config.imapHost} port=${config.imapPort} security=${config.imapSecurity} outcome=${outcome} reason=${reason}\n`);
374
+ };
294
375
  try {
295
376
  await client.connect();
377
+ // Socket is live once connect resolves — contain a late socket 'error' that
378
+ // could fire during logout/teardown.
379
+ containProbeSocket(client, "imap", token);
296
380
  await safeLogout(client);
381
+ attempt("ok", "none");
297
382
  return null;
298
383
  }
299
384
  catch (err) {
300
- // Never abandon the client: an unlistened half-established TLS socket
301
- // emitting a late 'error' kills the process.
385
+ // On the auth-reject path ImapFlow leaves the socket live; contain it before
386
+ // close() removes ImapFlow's own forwarder, so a late socket 'error' cannot
387
+ // escape as an uncaughtException once the census has returned to baseline.
388
+ containProbeSocket(client, "imap", token);
302
389
  try {
303
390
  client.close();
304
391
  }
305
392
  catch { /* contained */ }
306
- const msg = err instanceof Error ? err.message : String(err);
307
- if (msg.includes("AUTHENTICATIONFAILED") || msg.includes("Invalid credentials") || msg.includes("LOGIN failed")) {
308
- return `IMAP authentication failed for ${config.email}. Check the password — most providers require an app-specific password when 2FA is enabled.`;
309
- }
310
- if (msg.includes("ECONNREFUSED")) {
311
- return `IMAP connection refused at ${config.imapHost}:${config.imapPort}. Check that IMAP is enabled in your email provider settings.`;
312
- }
313
- if (msg.includes("ENOTFOUND")) {
314
- return `IMAP host ${config.imapHost} not found. Check the hostname.`;
315
- }
316
- if (msg.includes("certificate") || msg.includes("TLS") || msg.includes("SSL")) {
317
- return `IMAP TLS error connecting to ${config.imapHost}:${config.imapPort}. Try port 993 (TLS) or 143 (STARTTLS).`;
318
- }
319
- if (msg.includes("ETIMEDOUT") || msg.includes("timeout")) {
320
- return `IMAP connection timed out to ${config.imapHost}:${config.imapPort}. Check the host and port.`;
321
- }
322
- return `IMAP connection failed: ${msg}`;
393
+ const kind = classifyImapError(err);
394
+ attempt("fail", kind);
395
+ return imapFailureMessage(kind, config, err);
323
396
  }
324
397
  finally {
325
398
  releaseProbe("imap", token);
@@ -491,11 +564,15 @@ export async function fetchRecent(config, password, options = {}) {
491
564
  let envelopeCount = 0;
492
565
  let recipientKept = 0;
493
566
  let recipientHidden = 0;
567
+ // Body sources captured during streaming and parsed after the fetch
568
+ // iterator closes — running simpleParser inside the open fetch loop can
569
+ // stall the IMAP socket. Aligned by index with `messages`.
570
+ const bodySources = [];
494
571
  for await (const msg of client.fetch(uidList, {
495
572
  envelope: true,
496
573
  uid: true,
497
574
  bodyStructure: true,
498
- bodyParts: [{ key: "1", start: 0, maxLength: BODY_MAX_BYTES }],
575
+ source: true,
499
576
  }, { uid: true })) {
500
577
  envelopeCount++;
501
578
  diag("fetchRecent", `envelope #${envelopeCount}`, `uid=${msg.uid} from=${msg.envelope?.from?.[0]?.address ?? "?"} at ${Date.now() - t2}ms`);
@@ -516,12 +593,14 @@ export async function fetchRecent(config, password, options = {}) {
516
593
  continue;
517
594
  // Collect up to `limit` messages for display, but keep iterating past
518
595
  // 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.
596
+ // fetched window. Body parsing is deferred until the iterator closes
597
+ // (see bodySources) and only runs for the displayed messages.
521
598
  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
599
  const attachments = walkAttachmentMetadata(msg.bodyStructure);
600
+ bodySources.push({
601
+ source: msg.source ?? Buffer.alloc(0),
602
+ structureType: msg.bodyStructure?.type ?? "?",
603
+ });
525
604
  messages.push({
526
605
  uid: msg.uid,
527
606
  messageId,
@@ -529,11 +608,17 @@ export async function fetchRecent(config, password, options = {}) {
529
608
  from: fromAddr,
530
609
  subject,
531
610
  date,
532
- bodyPreview,
611
+ bodyPreview: "",
533
612
  attachments,
534
613
  });
535
614
  }
536
615
  }
616
+ // Parse the captured sources now that the fetch stream is closed.
617
+ for (let i = 0; i < messages.length; i++) {
618
+ const { body, kind } = await extractBody(bodySources[i].source);
619
+ messages[i].bodyPreview = previewBody(body);
620
+ diag("fetchRecent", `body #${i + 1}`, `uid=${messages[i].uid} preview=${messages[i].bodyPreview.length}chars type=${bodySources[i].structureType} bodySource=${kind}`);
621
+ }
537
622
  diag("fetchRecent", "fetch done", `${envelopeCount} envelopes, ${messages.length} messages in ${Date.now() - t2}ms (total ${Date.now() - t0}ms)`);
538
623
  if (clientSideRecipients) {
539
624
  diag("fetchRecent", "filter applied", `kept=${recipientKept} hidden=${recipientHidden} recipients=[${clientSideRecipients.join(",")}]`);
@@ -608,11 +693,14 @@ export async function searchMessages(config, password, query) {
608
693
  let envelopeCount = 0;
609
694
  let recipientKept = 0;
610
695
  let recipientHidden = 0;
696
+ // Body sources captured during streaming and parsed after the fetch
697
+ // iterator closes (see fetchRecent). Aligned by index with `messages`.
698
+ const bodySources = [];
611
699
  for await (const msg of client.fetch(recentUids.map(String).join(","), {
612
700
  envelope: true,
613
701
  uid: true,
614
702
  bodyStructure: true,
615
- bodyParts: [{ key: "1", start: 0, maxLength: BODY_MAX_BYTES }],
703
+ source: true,
616
704
  }, { uid: true })) {
617
705
  envelopeCount++;
618
706
  // Client-side recipient filter (replaces IMAP SEARCH TO), with kept-vs-hidden accounting
@@ -630,11 +718,13 @@ export async function searchMessages(config, password, query) {
630
718
  const inReplyTo = msg.envelope?.inReplyTo ?? null;
631
719
  // Collect up to `limit` messages for display, but keep iterating past
632
720
  // 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.
721
+ // fetched window. Body parsing is deferred until the iterator closes.
635
722
  if (messages.length < limit) {
636
- const bodyPreview = extractBodyFromFetch(msg);
637
723
  const attachments = walkAttachmentMetadata(msg.bodyStructure);
724
+ bodySources.push({
725
+ source: msg.source ?? Buffer.alloc(0),
726
+ structureType: msg.bodyStructure?.type ?? "?",
727
+ });
638
728
  messages.push({
639
729
  uid: msg.uid,
640
730
  messageId,
@@ -642,11 +732,17 @@ export async function searchMessages(config, password, query) {
642
732
  from: fromAddr,
643
733
  subject: subj,
644
734
  date,
645
- bodyPreview,
735
+ bodyPreview: "",
646
736
  attachments,
647
737
  });
648
738
  }
649
739
  }
740
+ // Parse the captured sources now that the fetch stream is closed.
741
+ for (let i = 0; i < messages.length; i++) {
742
+ const { body, kind } = await extractBody(bodySources[i].source);
743
+ messages[i].bodyPreview = previewBody(body);
744
+ diag("searchMessages", `body #${i + 1}`, `uid=${messages[i].uid} preview=${messages[i].bodyPreview.length}chars type=${bodySources[i].structureType} bodySource=${kind}`);
745
+ }
650
746
  diag("searchMessages", "fetch done", `${envelopeCount} envelopes, ${messages.length} messages in ${Date.now() - t2}ms (total ${Date.now() - t0}ms)`);
651
747
  if (clientSideRecipients) {
652
748
  diag("searchMessages", "filter applied", `kept=${recipientKept} hidden=${recipientHidden} recipients=[${clientSideRecipients.join(",")}]`);
@@ -728,11 +824,14 @@ export async function fetchSinceUid(config, password, sinceUid, recipients) {
728
824
  let hidden = 0;
729
825
  const t2 = Date.now();
730
826
  const uidList = uids.map(String).join(",");
827
+ // Body sources captured during streaming and parsed after the fetch
828
+ // iterator closes (see fetchRecent). Aligned by index with `emails`.
829
+ const bodySources = [];
731
830
  for await (const msg of client.fetch(uidList, {
732
831
  envelope: true,
733
832
  uid: true,
734
833
  bodyStructure: true,
735
- bodyParts: [{ key: "1", start: 0, maxLength: BODY_MAX_BYTES }],
834
+ source: true,
736
835
  }, { uid: true })) {
737
836
  // Track highest UID for high-water mark
738
837
  const uid = Number(msg.uid);
@@ -754,9 +853,18 @@ export async function fetchSinceUid(config, password, sinceUid, recipients) {
754
853
  const inReplyTo = msg.envelope?.inReplyTo ?? null;
755
854
  const toAddresses = (msg.envelope?.to ?? []).map((a) => a.address).filter((a) => !!a);
756
855
  const ccAddresses = (msg.envelope?.cc ?? []).map((a) => a.address).filter((a) => !!a);
757
- const bodyPreview = extractBodyFromFetch(msg);
758
856
  const attachments = walkAttachmentMetadata(msg.bodyStructure);
759
- emails.push({ uid, messageId, inReplyTo, toAddresses, ccAddresses, fromAddress: fromAddr, fromName, subject, date, bodyPreview, attachments });
857
+ bodySources.push({
858
+ source: msg.source ?? Buffer.alloc(0),
859
+ structureType: msg.bodyStructure?.type ?? "?",
860
+ });
861
+ emails.push({ uid, messageId, inReplyTo, toAddresses, ccAddresses, fromAddress: fromAddr, fromName, subject, date, bodyPreview: "", attachments });
862
+ }
863
+ // Parse the captured sources now that the fetch stream is closed.
864
+ for (let i = 0; i < emails.length; i++) {
865
+ const { body, kind } = await extractBody(bodySources[i].source);
866
+ emails[i].bodyPreview = previewBody(body);
867
+ diag("fetchSinceUid", `body #${i + 1}`, `uid=${emails[i].uid} preview=${emails[i].bodyPreview.length}chars type=${bodySources[i].structureType} bodySource=${kind}`);
760
868
  }
761
869
  diag("fetchSinceUid", "fetch done", `${emails.length} emails (maxUid=${maxUid}) in ${Date.now() - t2}ms (total ${Date.now() - t0}ms)`);
762
870
  if (recipients) {
@@ -843,6 +951,70 @@ export async function fetchEnvelopeByMessageId(config, password, messageId) {
843
951
  await safeLogout(client);
844
952
  }
845
953
  }
954
+ /**
955
+ * Fetch the COMPLETE decoded body of one message, addressed by UID (in a
956
+ * folder) or by Message-ID (resolved across INBOX and \Sent). Downloads the
957
+ * full RFC822 source and decodes it with mailparser — no 500-char cap and no
958
+ * MIME-shape blind spot. Returns null when the message cannot be located.
959
+ */
960
+ export async function fetchFullBody(config, password, target) {
961
+ let mailbox = "";
962
+ let uid;
963
+ if ("messageId" in target) {
964
+ const env = await fetchEnvelopeByMessageId(config, password, target.messageId);
965
+ if (!env)
966
+ return null;
967
+ mailbox = env.mailbox;
968
+ uid = env.uid;
969
+ }
970
+ else {
971
+ uid = target.uid;
972
+ }
973
+ const client = createClient(config, password);
974
+ const t0 = Date.now();
975
+ const label = "messageId" in target
976
+ ? `messageId=${target.messageId}`
977
+ : `uid=${target.uid} folder=${target.folder ?? "inbox"}`;
978
+ diag("fetchFullBody", "start", label);
979
+ try {
980
+ await client.connect();
981
+ if (!("messageId" in target)) {
982
+ mailbox = await resolveFolderName(client, target.folder ?? "inbox");
983
+ }
984
+ const lock = await client.getMailboxLock(mailbox);
985
+ try {
986
+ const msg = await client.fetchOne(String(uid), { uid: true, source: true, envelope: true }, { uid: true });
987
+ if (!msg || !msg.source) {
988
+ diag("fetchFullBody", "not-found", `uid=${uid} mailbox=${mailbox} in ${Date.now() - t0}ms`);
989
+ return null;
990
+ }
991
+ const src = msg.source;
992
+ const { body, kind } = await extractBody(src);
993
+ const env = msg.envelope ?? {};
994
+ diag("fetchFullBody", "done", `uid=${uid} mailbox=${mailbox} bytes=${src.length} bodyLen=${body.length} bodySource=${kind} in ${Date.now() - t0}ms`);
995
+ return {
996
+ uid: msg.uid,
997
+ messageId: env.messageId ?? null,
998
+ from: env.from?.[0]?.address ?? "(unknown)",
999
+ subject: env.subject ?? "(no subject)",
1000
+ date: env.date?.toISOString() ?? "(unknown date)",
1001
+ body,
1002
+ bodySource: kind,
1003
+ bytes: src.length,
1004
+ };
1005
+ }
1006
+ finally {
1007
+ lock.release();
1008
+ }
1009
+ }
1010
+ catch (err) {
1011
+ diag("fetchFullBody", "FAILED", `${err instanceof Error ? err.message : String(err)} after ${Date.now() - t0}ms`);
1012
+ throw err;
1013
+ }
1014
+ finally {
1015
+ await safeLogout(client);
1016
+ }
1017
+ }
846
1018
  /**
847
1019
  * Get the inbox message count.
848
1020
  */
@@ -851,6 +1023,7 @@ export async function getInboxCount(config, password, token = newProbeToken()) {
851
1023
  trackProbe("imap", client, token);
852
1024
  try {
853
1025
  await client.connect();
1026
+ containProbeSocket(client, "imap", token);
854
1027
  const mailbox = await client.mailboxOpen("INBOX");
855
1028
  return mailbox.exists;
856
1029
  }
@@ -944,33 +1117,66 @@ export async function fetchDraftSource(config, password, uid) {
944
1117
  }
945
1118
  }
946
1119
  /**
947
- * Read-modify-replace cycle for an existing draft addressed by Drafts-folder UID.
1120
+ * Read-modify-replace cycle for an existing draft. The recorded Drafts UID is a
1121
+ * fast-path hint; the draft's own `Message-ID` is the stable identity, because
1122
+ * Gmail renumbers a draft's UID when it is touched or re-synced.
1123
+ *
1124
+ * Resolution order, inside the Drafts lock: the recorded UID if still live, else
1125
+ * a `SEARCH HEADER Message-ID` relocate (when a draftMessageId is supplied),
1126
+ * else nothing. In every path the replacement is APPENDed (with the \Draft flag)
1127
+ * BEFORE the resolved old copy is expunged, so a mid-cycle failure never leaves
1128
+ * zero copies.
1129
+ *
1130
+ * When neither identity resolves a live draft, the prior draft is genuinely
1131
+ * gone: the replacement is appended as a fresh draft and `created:true` is
1132
+ * returned — no throw. `newUid` is the APPENDUID when the server supports
1133
+ * UIDPLUS, else null.
948
1134
  *
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.
1135
+ * A single transient transport drop is absorbed by the bounded retry; permanent
1136
+ * errors (auth) surface immediately.
954
1137
  */
955
- export async function replaceDraft(config, password, oldUid, newRaw) {
1138
+ export async function replaceDraft(config, password, oldUid, newRaw, draftMessageId) {
1139
+ return withTransientRetry("draftReplace", () => replaceDraftOnce(config, password, oldUid, newRaw, draftMessageId ?? null));
1140
+ }
1141
+ async function replaceDraftOnce(config, password, oldUid, newRaw, draftMessageId) {
956
1142
  const client = createClient(config, password);
957
1143
  const t0 = Date.now();
958
- diag("draftReplace", "start", `oldUid=${oldUid} bytes=${newRaw.length}`);
1144
+ diag("draftReplace", "start", `oldUid=${oldUid} bytes=${newRaw.length} draftMessageId=${draftMessageId ?? "none"}`);
959
1145
  try {
960
1146
  await client.connect();
961
1147
  const folder = await resolveFolderName(client, "drafts");
962
1148
  const lock = await client.getMailboxLock(folder);
963
1149
  try {
1150
+ // Resolve the live UID: fast-path the recorded UID, else relocate by the
1151
+ // draft's own Message-ID, else accept that the prior draft is gone.
1152
+ let resolvedUid = null;
1153
+ let addressing = "created-fresh";
964
1154
  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}.`);
1155
+ if (existing) {
1156
+ resolvedUid = oldUid;
1157
+ addressing = "uid-hit";
967
1158
  }
1159
+ else if (draftMessageId) {
1160
+ const needle = `<${draftMessageId.replace(/^<|>$/g, "")}>`;
1161
+ const uids = await client.search({ header: { "message-id": needle } }, { uid: true });
1162
+ if (uids && uids.length > 0) {
1163
+ resolvedUid = uids[0];
1164
+ addressing = "message-id-relocate";
1165
+ }
1166
+ }
1167
+ // Append the replacement first in every path — the atomicity invariant.
968
1168
  const appended = await client.append(folder, newRaw, ["\\Draft"]);
969
1169
  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 };
1170
+ if (resolvedUid === null) {
1171
+ // Create-on-absent recovery: distinct marker so it is not read as a
1172
+ // normal replace. Nothing to expunge.
1173
+ diag("draftReplace", "created-fresh", `folder=${folder} newUid=${newUid ?? "not-reported"} oldUid=${oldUid} draftMessageId=${draftMessageId ?? "none"} in ${Date.now() - t0}ms`);
1174
+ return { folder, oldUid, newUid, expunged: false, created: true, addressing };
1175
+ }
1176
+ diag("draftReplace", "appended", `folder=${folder} newUid=${newUid ?? "not-reported"} addressing=${addressing} resolvedUid=${resolvedUid}`);
1177
+ const expunged = await client.messageDelete(String(resolvedUid), { uid: true });
1178
+ diag("draftReplace", "expunged", `resolvedUid=${resolvedUid} newUid=${newUid ?? "not-reported"} addressing=${addressing} expunge=${expunged} in ${Date.now() - t0}ms`);
1179
+ return { folder, oldUid, newUid, expunged: !!expunged, created: false, addressing };
974
1180
  }
975
1181
  finally {
976
1182
  lock.release();
@@ -986,10 +1192,18 @@ export async function replaceDraft(config, password, oldUid, newRaw) {
986
1192
  }
987
1193
  /**
988
1194
  * 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.
1195
+ * after SMTP accepts the message). An absent UID is treated as success with
1196
+ * nothing removed (`deleted:false`) deletion's goal is already met, so a
1197
+ * stale UID is not an error. A single transient transport drop is absorbed by
1198
+ * the bounded retry.
1199
+ *
1200
+ * Message-ID relocation is deliberately not applied here: the send path
1201
+ * addresses by UID only and carries no draft Message-ID to relocate by.
991
1202
  */
992
1203
  export async function deleteDraft(config, password, uid) {
1204
+ return withTransientRetry("draftDelete", () => deleteDraftOnce(config, password, uid));
1205
+ }
1206
+ async function deleteDraftOnce(config, password, uid) {
993
1207
  const client = createClient(config, password);
994
1208
  const t0 = Date.now();
995
1209
  diag("draftDelete", "start", `uid=${uid}`);
@@ -1000,7 +1214,8 @@ export async function deleteDraft(config, password, uid) {
1000
1214
  try {
1001
1215
  const existing = await client.fetchOne(String(uid), { uid: true }, { uid: true });
1002
1216
  if (!existing) {
1003
- throw new Error(`Draft UID ${uid} not found in ${folder}.`);
1217
+ diag("draftDelete", "absent", `uid=${uid} folder=${folder} in ${Date.now() - t0}ms`);
1218
+ return { folder, deleted: false };
1004
1219
  }
1005
1220
  const deleted = await client.messageDelete(String(uid), { uid: true });
1006
1221
  diag("draftDelete", "deleted", `uid=${uid} folder=${folder} deleted=${deleted} in ${Date.now() - t0}ms`);