@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.
- package/dist/__tests__/claude-ptys-slice.test.js +36 -1
- package/dist/__tests__/neo4j-pidfile-scrub.test.js +103 -0
- package/dist/index.js +42 -7
- package/dist/neo4j-pidfile-scrub.js +90 -0
- 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 +61 -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/admin-ui.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__/email-status.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-status.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-status.test.js +58 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-status.test.js.map +1 -0
- 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__/imap-probe.test.js +109 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-probe.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/credentials.d.ts +13 -0
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +35 -0
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +83 -30
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +327 -112
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.d.ts +19 -0
- package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.js +33 -0
- package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.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/dist/tools/email-setup.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +11 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-status.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-status.js +24 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-status.js.map +1 -1
- package/payload/platform/plugins/email/mcp/package.json +2 -0
- package/payload/platform/plugins/email/references/email-reference.md +30 -3
- 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 +13 -5
- 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 +46 -11
- 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-tHyAPNnQ.js} +1 -1
- package/payload/server/public/assets/AdminShell-Ce9izNVZ.js +1 -0
- package/payload/server/public/assets/{Checkbox-O1qvMk_u.js → Checkbox-Cq6SJJlX.js} +1 -1
- package/payload/server/public/assets/OperatorConversations-BERW_hUa.css +1 -0
- package/payload/server/public/assets/OperatorConversations-m2lz6Ss7.js +9 -0
- package/payload/server/public/assets/admin-D9iMgdbN.js +1 -0
- 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-CZWi0EAP.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-DUAh-_kx.js +1 -0
- package/payload/server/public/assets/channel-B_-5bau1.js +1 -0
- package/payload/server/public/assets/chat-DHuibPfc.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-CoOcAR_2.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-Dc_votDd.js} +3 -3
- package/payload/server/public/assets/{graph-labels-BNnhRQqM.js → graph-labels-jqagxwhW.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-KYXcuQ4Y.js +1 -0
- 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-Cfsv97nq.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-49guEjCh.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-CyR5o3Nq.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/AdminShell-CDQDkT1R.js +0 -1
- 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/admin-BkYc54bi.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Csj53u5T.js +0 -1
- package/payload/server/public/assets/calendar-DgP7lal2.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/operator-DeCk_qYw.js +0 -1
- 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,5 +1,6 @@
|
|
|
1
1
|
import { ImapFlow } from "imapflow";
|
|
2
|
-
import {
|
|
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
|
-
*
|
|
225
|
-
*
|
|
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
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
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
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
//
|
|
301
|
-
//
|
|
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
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
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
|
|
520
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
950
|
-
*
|
|
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 (
|
|
966
|
-
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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).
|
|
990
|
-
*
|
|
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
|
-
|
|
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`);
|