@rubytech/create-maxy-code 0.1.357 → 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.
- package/dist/__tests__/claude-ptys-slice.test.js +36 -1
- package/dist/port-resolution.js +18 -1
- package/package.json +1 -1
- package/payload/platform/package-lock.json +237 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js +79 -1
- package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +17 -3
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts +16 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js +52 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +59 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +7 -1
- package/payload/platform/plugins/cloudflare/bin/__tests__/cf-token.test.sh +195 -0
- package/payload/platform/plugins/cloudflare/bin/cf-token.sh +157 -0
- package/payload/platform/plugins/cloudflare/references/api.md +2 -0
- package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +14 -0
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +10 -1
- package/payload/platform/plugins/docs/PLUGIN.md +2 -0
- package/payload/platform/plugins/docs/references/claudeai-connectors.md +45 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +6 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
- package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/email/PLUGIN.md +7 -3
- package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.js +99 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/body-extract.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.js +32 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/compose-draft-id.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-edit.test.js +64 -6
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-edit.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft.test.js +12 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +94 -9
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.js +4 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/index.js +39 -2
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts +5 -0
- package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/compose.js +8 -1
- package/payload/platform/plugins/email/mcp/dist/lib/compose.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +68 -33
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +253 -97
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts +6 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.js +4 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.d.ts +8 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.js +10 -6
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-edit.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft.js +4 -2
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.d.ts +15 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.js +29 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-fetch-body.js.map +1 -0
- package/payload/platform/plugins/email/mcp/package.json +2 -0
- package/payload/platform/plugins/email/references/email-reference.md +21 -1
- package/payload/platform/scripts/rss-sampler.sh +21 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/connector-auth.d.ts +31 -0
- package/payload/platform/services/claude-session-manager/dist/connector-auth.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/connector-auth.js +49 -0
- package/payload/platform/services/claude-session-manager/dist/connector-auth.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +24 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +214 -17
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +2 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +24 -4
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.d.ts +1 -0
- package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.js +28 -0
- package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.js +75 -5
- package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-BW1UgfoI.js → AdminLoginScreens-Ro_RUTDh.js} +1 -1
- package/payload/server/public/assets/{AdminShell-CDQDkT1R.js → AdminShell-CmqhpAPU.js} +1 -1
- package/payload/server/public/assets/{Checkbox-O1qvMk_u.js → Checkbox-dYLaMEdh.js} +1 -1
- package/payload/server/public/assets/OperatorConversations-2zJh7qnO.css +1 -0
- package/payload/server/public/assets/OperatorConversations-Cei12sSK.js +9 -0
- package/payload/server/public/assets/{admin-BkYc54bi.js → admin-BldZQrJF.js} +1 -1
- package/payload/server/public/assets/{arc-DpkSQNto.js → arc-Crkz45LN.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Dma4Bgha.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Bw7Yq8O9.js → architectureDiagram-Q4EWVU46-C9JThHgP.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BBtJQFSz.js → blockDiagram-DXYQGD6D-DpsV_HXT.js} +1 -1
- package/payload/server/public/assets/{browser-CySvj140.js → browser-DrXZ4wpy.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-JAMgxzOr.js → c4Diagram-AHTNJAMY-CTm_6aKi.js} +1 -1
- package/payload/server/public/assets/{calendar-DgP7lal2.js → calendar-CQ4TCbO7.js} +1 -1
- package/payload/server/public/assets/channel-B_-5bau1.js +1 -0
- package/payload/server/public/assets/chat-D7w6_NE1.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-eDugsc2L.js → chunk-2KRD3SAO-1KSJW1q6.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-BXfECwdC.js → chunk-336JU56O-C3t08Ip-.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-Dirxdwjh.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-CByVeKCY.js → chunk-4BX2VUAB-kl1ze33n.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-CpY0DcUB.js → chunk-4TB4RGXK-DxpoDoHg.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-C3YxgyIb.js → chunk-55IACEB6-WM9VJzha.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-Bc0d7j8A.js → chunk-5FUZZQ4R-CLH63ZH3.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-CTUGMRIa.js → chunk-5PVQY5BW-DCTb8IAT.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-CbdPnpLd.js → chunk-67CJDMHE-BSYzyaoH.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-Cjl2kevz.js → chunk-7N4EOEYR-CarJ7O2P.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-BgozIh5p.js → chunk-AA7GKIK3-DTNLyBpH.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-6Ufq45NW.js → chunk-BSJP7CBP-BFmHflHY.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-BhhYuMnA.js → chunk-CIAEETIT-BpjG0IzU.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-BfaJ3yDD.js → chunk-EDXVE4YY-2fSowhlW.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-C7-hIGVC.js → chunk-ENJZ2VHE-ClcranGV.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-BQvvyocF.js → chunk-FMBD7UC4-DLq4BItQ.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-C6rBDOxH.js → chunk-FOC6F5B3-B9QI3Guz.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-BjOJvkVa.js → chunk-ICPOFSXX-BAr-Mv1Z.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-kKowRHhn.js → chunk-K5T4RW27-8PvAHasY.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-bAEV_5eH.js → chunk-KGLVRYIC--aGaDRH5.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-BLvsQAfg.js → chunk-LIHQZDEY-BrWj7mw2.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-BzYZfPZ7.js → chunk-ORNJ4GCN-kYQmkMTi.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-CL7WRw42.js → chunk-OYMX7WX6-CIsryCWH.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-Duipo1kI.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-BdcVGjau.js → chunk-U2HBQHQK-68X653e3.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-C0CL_Jin.js → chunk-X2U36JSP-v3v9soX3.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-CsV7CUxH.js → chunk-XPW4576I-CW5DPpop.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-Cj0_fLkR.js → chunk-YZCP3GAM-B1VP7Rl2.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-DLuquyoY.js → chunk-ZZ45TVLE-DiJrujNY.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-B3kX5SuB.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-D761c7Lr.js +1 -0
- package/payload/server/public/assets/clone-BrhFv6Tb.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-BU3EOLXX.js → cose-bilkent-S5V4N54A-BscXxLMc.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-CoCAYhFc.js → dagre-KV5264BT-DVgGlTQ1.js} +1 -1
- package/payload/server/public/assets/{dagre-BRq8C9NU.js → dagre-MBv6WlCS.js} +1 -1
- package/payload/server/public/assets/data-Dc5XNWgF.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-DBy5GNaQ.js → diagram-5BDNPKRD-BmhCaO0a.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-l93MsBLv.js → diagram-G4DWMVQ6-ChU9L8vz.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-BIfXkVlL.js → diagram-MMDJMWI5-Oy409Zc_.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-BuTJLBny.js → diagram-TYMM5635-Cn1YByqk.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-BwPxWDbP.js → erDiagram-SMLLAGMA-e17yfLvr.js} +1 -1
- package/payload/server/public/assets/{flatten-DtM0XAMt.js → flatten-Cl1Bc3dR.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BhPtHXMR.js → flowDiagram-DWJPFMVM-Dqt7sCVZ.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BlXjprdD.js → ganttDiagram-T4ZO3ILL-B4IhwS2r.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-BCQAqtLy.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BW36BDiu.js → gitGraphDiagram-UUTBAWPF-BDw-PKcI.js} +1 -1
- package/payload/server/public/assets/{graph-uFhGyOZR.js → graph-l_3_j3I9.js} +3 -3
- package/payload/server/public/assets/{graph-labels-BNnhRQqM.js → graph-labels-CJj55nHk.js} +1 -1
- package/payload/server/public/assets/{graphlib-Bjg3Tq-k.js → graphlib-DPIYk_sg.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-qU7hK50T.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-KGdcJeTs.js +2 -0
- package/payload/server/public/assets/{isEmpty-D__iH1WQ.js → isEmpty-D1pGOD1J.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CmB-bA_O.js → ishikawaDiagram-UXIWVN3A-BRIHsB3G.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-Bt1fJKmc.js → journeyDiagram-VCZTEJTY-aKNgmoHd.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DVbksz_J.js → kanban-definition-6JOO6SKY-cwBwT-nm.js} +1 -1
- package/payload/server/public/assets/{line-BX2Ugx3B.js → line-DVtVRzUA.js} +1 -1
- package/payload/server/public/assets/{linear-Bdc-QJWI.js → linear-DtoOFPO2.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-qv1e-J5x.js → mermaid-parser.core-G7YD_zO3.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-C_d9YQX_.js → mermaid.core-DpFyndHm.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DwKGvF14.js → mindmap-definition-QFDTVHPH-CYXK_Pvz.js} +1 -1
- package/payload/server/public/assets/{operator-DeCk_qYw.js → operator-CKmOgXNa.js} +1 -1
- package/payload/server/public/assets/{ordinal-BRQ5CzZY.js → ordinal-CBZeH4Yp.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-DiU2yS9m.js +1 -0
- package/payload/server/public/assets/page-hG7xTrcO.js +32 -0
- package/payload/server/public/assets/pie-ZZUOXDRM-zHUKKlVv.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-dUvQC_4S.js → pieDiagram-DEJITSTG-DFo4N6WH.js} +1 -1
- package/payload/server/public/assets/{public-DgltXhnD.js → public-smrhkkxD.js} +1 -1
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CebX_oPa.js → quadrantDiagram-34T5L4WZ-BlH5iIhl.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-BUnVoaPg.js +1 -0
- package/payload/server/public/assets/{reduce-CkcXOmmJ.js → reduce-PuPlFPRX.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-h2euIqGj.js → requirementDiagram-MS252O5E-D5WLty8A.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-Dd8aAvU7.js +1 -0
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DCii3fkF.js → sankeyDiagram-XADWPNL6-vB85IxHG.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-CQSGYmlq.js → sequenceDiagram-FGHM5R23-Cxraa0Ee.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-CVtU0iXH.js → stateDiagram-FHFEXIEX-BBPssYBx.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BWmmUwDt.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-ChvsBltI.js → timeline-definition-GMOUNBTQ-Bgk6PT62.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-C3Y-Q52h.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-Dk0qZpAg.js +1 -0
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-e-EZiiba.js → vennDiagram-DHZGUBPP-B-APTs3L.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-BAWI02OA.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-4JaRn1s5.js → wardleyDiagram-NUSXRM2D-CkXJSs_i.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-kxwmCNuM.js → xychartDiagram-5P7HB3ND-BkyshPMV.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +4 -4
- package/payload/server/public/chat.html +7 -6
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +7 -6
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/operator.html +9 -8
- package/payload/server/public/public.html +7 -6
- package/payload/server/server.js +244 -24
- package/payload/server/public/assets/OperatorConversations-CVH-FoJo.css +0 -1
- package/payload/server/public/assets/OperatorConversations-jqaPCHwB.js +0 -9
- package/payload/server/public/assets/architecture-YZFGNWBL-Csj53u5T.js +0 -1
- package/payload/server/public/assets/channel-BUQUAUts.js +0 -1
- package/payload/server/public/assets/chat-gls5aV50.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-CdknReB2.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-8tHoEDE9.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BEGZ3DLg.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DsLxnm5U.js +0 -1
- package/payload/server/public/assets/clone-uiXbH_kh.js +0 -1
- package/payload/server/public/assets/data-CbI8-CX3.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-D_2KrZoP.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-BP2TNwQ4.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-D1kDtBOY.js +0 -2
- package/payload/server/public/assets/packet-4T2RLAQJ-BDg8fde8.js +0 -1
- package/payload/server/public/assets/page-jGwq-DpW.js +0 -32
- package/payload/server/public/assets/pie-ZZUOXDRM-CXDyYMkZ.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-xen_zv8F.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BywcXjtb.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-2FBf_1It.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-BDdb30P-.js +0 -1
- package/payload/server/public/assets/wardley-RL74JXVD-BLq5MxjR.js +0 -1
- /package/payload/server/public/assets/{_baseFor-SRtUHe7G.js → _baseFor-brRjpUOX.js} +0 -0
- /package/payload/server/public/assets/{array-BpsnM-Py.js → array-BGFCBI0e.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-D4Rl1H5h.js → cytoscape.esm-TvRJ2tGX.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-C7sQPl-O.js → defaultLocale-CRZydyG6.js} +0 -0
- /package/payload/server/public/assets/{dist-CtUNt3w8.js → dist-B0CkUodR.js} +0 -0
- /package/payload/server/public/assets/{init-BydxaDEV.js → init-B8gtcn7T.js} +0 -0
- /package/payload/server/public/assets/{katex-B0fVeDx4.js → katex-RxgSu_dy.js} +0 -0
- /package/payload/server/public/assets/{path-jlWYQ2i9.js → path-DZF-JdEe.js} +0 -0
- /package/payload/server/public/assets/{preload-helper-uTix4PVD.js → preload-helper-CQ36nxok.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
- /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
|
-
|
|
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
|
|
520
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
950
|
-
* the
|
|
951
|
-
*
|
|
952
|
-
*
|
|
953
|
-
*
|
|
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 (
|
|
966
|
-
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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).
|
|
990
|
-
*
|
|
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
|
-
|
|
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`);
|