@rubytech/create-maxy-code 0.1.280 → 0.1.281
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/package.json +1 -1
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts +31 -43
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js +112 -85
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-spawn-tee/src/__tests__/spawn-tee.test.ts +111 -0
- package/payload/platform/lib/mcp-spawn-tee/src/index.ts +113 -84
- package/payload/platform/lib/mcp-spawn-tee/tsconfig.json +2 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -1
- package/payload/platform/plugins/docs/references/platform.md +2 -0
- package/payload/platform/plugins/email/PLUGIN.md +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.js +68 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-setup.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-setup.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-setup.test.js +55 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-setup.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/providers.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/providers.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/providers.test.js +110 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/providers.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/recipient-filter.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/recipient-filter.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/recipient-filter.test.js +57 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/recipient-filter.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/index.js +8 -1
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts +5 -0
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js +35 -5
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +13 -8
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +26 -15
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/ingest-batch.d.ts +8 -0
- package/payload/platform/plugins/email/mcp/dist/lib/ingest-batch.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/ingest-batch.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts +16 -5
- package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/providers.js +120 -13
- package/payload/platform/plugins/email/mcp/dist/lib/providers.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.js +3 -3
- package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +5 -3
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.js +6 -3
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.js.map +1 -1
- 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 +22 -4
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
- package/payload/platform/plugins/email/mcp/package.json +4 -2
- package/payload/platform/plugins/email/mcp/vitest.config.ts +9 -0
- package/payload/platform/plugins/email/references/email-reference.md +22 -5
- package/payload/platform/plugins/email/skills/email-composition/SKILL.md +1 -1
- package/payload/platform/plugins/graph/PLUGIN.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/kd-classify-account-dir.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/kd-classify-account-dir.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/kd-classify-account-dir.test.js +31 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/kd-classify-account-dir.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/kd-classify.d.ts +5 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/kd-classify.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/kd-classify.js +21 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/kd-classify.js.map +1 -1
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js +5 -5
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +7 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/server/public/assets/AdminShell-FO766lOW.js +1 -0
- package/payload/server/public/assets/{Checkbox-2reCESkb.js → Checkbox-syHoU9nY.js} +1 -1
- package/payload/server/public/assets/{admin-BpnsTEa3.js → admin-0_VxffD5.js} +1 -1
- package/payload/server/public/assets/{arc-BgelqC_3.js → arc-BW1WhwmV.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-xHbVjatf.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-BabnAZJm.js → architectureDiagram-Q4EWVU46-DsionCio.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BJzZqNsy.js → blockDiagram-DXYQGD6D-NOG5yVZk.js} +1 -1
- package/payload/server/public/assets/{browser-fgBRZ5gK.js → browser-Cxjkhtsk.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-By-xuh9F.js → c4Diagram-AHTNJAMY-DwFV1k9I.js} +1 -1
- package/payload/server/public/assets/channel-D8Go_zJk.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-Gj2dSpBl.js → chunk-2KRD3SAO-CKn-bEkI.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-2PXVPCUA.js → chunk-336JU56O-iJ4ISPCi.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-sWe0Iexz.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-9XIBlnC2.js → chunk-4BX2VUAB-B2mSmC97.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-Cr3mXPnI.js → chunk-4TB4RGXK-kKNYi2_i.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-Ccg46J3A.js → chunk-55IACEB6-JLT9m7Hd.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-Cz9MvPKc.js → chunk-5FUZZQ4R-Car3r0he.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-BleOdlgO.js → chunk-5PVQY5BW-DR9EfLvj.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-CyCYCQWy.js → chunk-67CJDMHE-CCdP3Pdx.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-KpT3uHzH.js → chunk-7N4EOEYR-bXgVzdM9.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-BfOzbuxq.js → chunk-AA7GKIK3-CYeDvY8B.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-Bq0M3wlv.js → chunk-BSJP7CBP-U0Fi-Ers.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-CR-2dNaL.js → chunk-CIAEETIT-C8QrV6Gg.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-egUGMTOS.js → chunk-EDXVE4YY-DLUWVRZV.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-CQohBEFw.js → chunk-ENJZ2VHE-B4bNJo6C.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-DIxBUbLP.js → chunk-FMBD7UC4-DxAQS3UB.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-C2694Zoq.js → chunk-FOC6F5B3-CyIK3zeY.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-DpXZKHyn.js → chunk-ICPOFSXX-Qzl8Ki0l.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-CUicG0Rp.js → chunk-K5T4RW27-7GcDzjyJ.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-B5mPJhOr.js → chunk-KGLVRYIC-B7pAr9hf.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-q81QhuMs.js → chunk-LIHQZDEY-DEoU2jrz.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-DgurdFZs.js → chunk-ORNJ4GCN-BwMFuN24.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-CxqMRu2d.js → chunk-OYMX7WX6-BenBO6O_.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-CHE_iZO7.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-BSLctJC9.js → chunk-U2HBQHQK-DewM8R0D.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP--0AUeeFl.js → chunk-X2U36JSP-C22Q6Ea4.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-D-hf9Bcn.js → chunk-XPW4576I-BA0a8Ygs.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-PvOhSwI9.js → chunk-YZCP3GAM-DfUUNTnA.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-1zPUlNWz.js → chunk-ZZ45TVLE-CUT6zma2.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BLb1mzrT.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Ctj5qYTS.js +1 -0
- package/payload/server/public/assets/clone-DGZ2vydA.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-Cvo-gkBd.js → cose-bilkent-S5V4N54A-Df_JmB8E.js} +1 -1
- package/payload/server/public/assets/{dagre-CMHsNfjP.js → dagre-CYXRIdv0.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-CM1DSx4E.js → dagre-KV5264BT-D4EGb60h.js} +1 -1
- package/payload/server/public/assets/{data-Cuynp6Y6.js → data-BOgwOWnw.js} +1 -1
- package/payload/server/public/assets/{diagram-5BDNPKRD-VDHiHwEI.js → diagram-5BDNPKRD-BzieCEfT.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-OKdufoeW.js → diagram-G4DWMVQ6-DDbO71XD.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-Yg6QdCA1.js → diagram-MMDJMWI5-DqQ_UXDA.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-CpEMZQDu.js → diagram-TYMM5635-CFyxA1aZ.js} +1 -1
- package/payload/server/public/assets/{dist-811BIK0R.js → dist-BmvZ8OaX.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-Dw5gAkFM.js → erDiagram-SMLLAGMA-BMaEBbOK.js} +1 -1
- package/payload/server/public/assets/{flatten-BsWEYbBB.js → flatten-Bo6YRmWl.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-Cy_R1XHz.js → flowDiagram-DWJPFMVM-De5ChZQP.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BYARP_eH.js → ganttDiagram-T4ZO3ILL-CXEMPVaK.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CmHdoS27.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BGOe0unY.js → gitGraphDiagram-UUTBAWPF-OZedr_6y.js} +1 -1
- package/payload/server/public/assets/{graph-Ddg24bjy.js → graph-BqD2GjQd.js} +2 -2
- package/payload/server/public/assets/{graph-labels-eOrWYy0R.js → graph-labels-DSDTSBfM.js} +1 -1
- package/payload/server/public/assets/{graphlib-DaefxCga.js → graphlib-CN4nhD2U.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-DTA9msO-.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-CSqg4nRL.js +2 -0
- package/payload/server/public/assets/{isEmpty-BWl67LAZ.js → isEmpty-D6Kr-M1M.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-YrGuzpiI.js → ishikawaDiagram-UXIWVN3A-ChlzrZyx.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-EIykOcqg.js → journeyDiagram-VCZTEJTY-DsrK55ZO.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BR0TC6Je.js → kanban-definition-6JOO6SKY-DYF-_Rym.js} +1 -1
- package/payload/server/public/assets/{line-DPGcT5IM.js → line-3zmwI8XK.js} +1 -1
- package/payload/server/public/assets/{linear-CPN03uSh.js → linear-COm19-p7.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-D7Iu4c9M.js → mermaid-parser.core-BrcX8-cl.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-krXRpXn9.js → mermaid.core-CBdz3b1N.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-HwiCtPzN.js → mindmap-definition-QFDTVHPH-BqCqlYZb.js} +1 -1
- package/payload/server/public/assets/{ordinal-krseTxxN.js → ordinal-BDi6f4xk.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-6GjqIKgu.js +1 -0
- package/payload/server/public/assets/pie-ZZUOXDRM-T72DJ5JR.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-xodxyWLe.js → pieDiagram-DEJITSTG-D5LiILuM.js} +1 -1
- package/payload/server/public/assets/{public-xVaPcg6i.js → public-udi_Z7la.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-DuYYY4ZZ.js → quadrantDiagram-34T5L4WZ-C3iuI5l_.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-x-6xW1dE.js +1 -0
- package/payload/server/public/assets/{reduce-tk-xY6Fv.js → reduce-CGi9ik8i.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-D0ta3kru.js → requirementDiagram-MS252O5E-CTPOw5qQ.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-KdHdlzvT.js → sankeyDiagram-XADWPNL6-D_5aVlYp.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DoSFUTQZ.js → sequenceDiagram-FGHM5R23-D7h5vocM.js} +1 -1
- package/payload/server/public/assets/{src-Cpl1JzME.js → src-DLEIsjER.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-BLW0KiSA.js → stateDiagram-FHFEXIEX-DyZjuwPj.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BiW9pGXy.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-8QAzhYz1.js → timeline-definition-GMOUNBTQ-BqpVYJz_.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-5-6wEryJ.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-DF48Cc86.js +1 -0
- package/payload/server/public/assets/{useSelectionMode-DlJsX-_X.js → useSelectionMode-BPzPjKP_.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-BKvfScOi.js → vennDiagram-DHZGUBPP-Czflml0e.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-duKIa6Hq.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-DaMfDpJ3.js → wardleyDiagram-NUSXRM2D-Cqz3pSTK.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BIkNQlLa.js → xychartDiagram-5P7HB3ND-CS5dEuUO.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +5 -5
- package/payload/server/public/public.html +4 -4
- package/payload/server/server.js +19 -39
- package/payload/server/public/assets/AdminShell-C6_5h1cC.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-YM-TtgPY.js +0 -1
- package/payload/server/public/assets/channel-BRPH0atd.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-QhauxjvK.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-BDAQvtxz.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BJv89PB6.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DNUszH5M.js +0 -1
- package/payload/server/public/assets/clone-CREg9jLM.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CtOPqykB.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-Jba5enA8.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-Df9BlkPA.js +0 -2
- package/payload/server/public/assets/packet-4T2RLAQJ-D_eWzeAP.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-BDiy1Ob2.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-C5mlfmtg.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-C5zlCV4X.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CM-9m-cS.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-DDHPB-Xh.js +0 -1
- package/payload/server/public/assets/wardley-RL74JXVD-CUb3Br4q.js +0 -1
- package/payload/server/public/assets/whatsapp-BE79pNh8.js +0 -1
- package/payload/server/public/whatsapp.html +0 -18
- /package/payload/server/public/assets/{_baseFor-BHtDrjIo.js → _baseFor-Cam2PbSt.js} +0 -0
- /package/payload/server/public/assets/{array-DetWRiSa.js → array-DYRGGQae.js} +0 -0
- /package/payload/server/public/assets/{chunk-CWOGyPgy.js → chunk-CnGqDkHZ.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-C9yNhe1u.js → cytoscape.esm-nWsJMTNI.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-_WRwicXn.js → defaultLocale-Du1XY3Dp.js} +0 -0
- /package/payload/server/public/assets/{init-sTEcj9YX.js → init-B5BXBRcm.js} +0 -0
- /package/payload/server/public/assets/{katex-s61Rgv6l.js → katex-HOUACuRw.js} +0 -0
- /package/payload/server/public/assets/{path-B0Ik7Tu9.js → path-CNO468J-.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-DKRO8IF-.js → rough.esm-DRO6hWPh.js} +0 -0
|
@@ -1,53 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* MCP spawn-tee — parent-side stderr capture
|
|
3
|
+
* MCP spawn-tee — parent-side stderr capture + lifecycle observability.
|
|
4
4
|
*
|
|
5
|
-
* Claude Code
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* flushes, so the per-server log file is empty and the platform's
|
|
12
|
-
* `[mcp-init-error] tail="(no stderr file)"` probe is operationally useless.
|
|
13
|
-
* That class shipped as the chronic memory-MCP silent-fail loop fixed by
|
|
14
|
-
* (and hit graph — solved there with per-plugin
|
|
15
|
-
* `appendFileSync` discipline that this wrapper now generalises).
|
|
5
|
+
* Claude Code spawns each MCP server itself; the platform never holds a
|
|
6
|
+
* ChildProcess handle. This wrapper sits between Claude Code and the real
|
|
7
|
+
* MCP server: Claude Code runs `node <this> <real-entry>`, and the wrapper
|
|
8
|
+
* spawns the real entry with `stdio:['inherit','inherit','pipe']` so stdin
|
|
9
|
+
* and stdout (the JSON-RPC channel) pass through byte-identical. Only stderr
|
|
10
|
+
* is intercepted, and only for logging.
|
|
16
11
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `${LOG_DIR}/mcp-${name}-stderr-<date>.log`. Synchronous writes survive
|
|
21
|
-
* `process.exit` because the kernel queues the syscall before the call
|
|
22
|
-
* returns. The chunk is also written to the wrapper's own stderr so
|
|
23
|
-
* Claude Code's existing stderr consumer is unchanged — the mechanism is
|
|
24
|
-
* additive, not interceptive.
|
|
12
|
+
* Because the wrapper IS the helper's parent, it observes the helper's true
|
|
13
|
+
* exit code, signal, and lifetime for every death mode — including a SIGKILL
|
|
14
|
+
* or a transport-layer crash that an in-process handler would miss.
|
|
25
15
|
*
|
|
26
|
-
* Claude Code CLI
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* wrapper (this file) — argv[2] = real entry, env.MCP_SPAWN_TEE_NAME = name
|
|
30
|
-
* │ spawns child with stdio:['inherit','inherit','pipe']
|
|
31
|
-
* ▼
|
|
32
|
-
* child = node <real-entry>
|
|
33
|
-
* │
|
|
34
|
-
* ├──▶ stdin/stdout: inherited from wrapper (Claude Code pipe)
|
|
35
|
-
* └──▶ stderr → wrapper.on('data', chunk =>)
|
|
36
|
-
* ├──▶ appendFileSync(${LOG_DIR}/mcp-${name}-stderr-<date>.log)
|
|
37
|
-
* └──▶ process.stderr.write(chunk) → Claude Code consumer
|
|
16
|
+
* Claude Code CLI → wrapper (this file) → child = node <real-entry>
|
|
17
|
+
* child stdin/stdout : inherited (Claude Code pipe, untouched)
|
|
18
|
+
* child stderr : piped → per-date log + per-session log + passthrough
|
|
38
19
|
*
|
|
39
|
-
*
|
|
40
|
-
* -
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
20
|
+
* Destinations (Task 706):
|
|
21
|
+
* - `${LOG_DIR}/mcp-<name>-stderr-<date>.log` — per-date raw (back-compat;
|
|
22
|
+
* the in-process mcp-stderr-tee and the [mcp-init-error] probe read it).
|
|
23
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — per-session raw + lifecycle
|
|
24
|
+
* lines; wrapper-only, so it never mixes the enumeration copy with the
|
|
25
|
+
* live copy. Authoritative sink. Absent when SESSION_ID is unset
|
|
26
|
+
* (enumeration spawn) — then the per-date file is the fallback.
|
|
27
|
+
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
28
|
+
* the [mcp-helper] lifecycle lines. A dropped POST loses nothing because
|
|
29
|
+
* the per-session file already holds the line (written synchronously).
|
|
47
30
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
31
|
+
* Lifecycle lines, correlation key `session=<id8> server=<name>`:
|
|
32
|
+
* [mcp-helper] op=spawn ... pid= entry=
|
|
33
|
+
* [mcp-helper] op=boot ... head=<first stderr bytes>
|
|
34
|
+
* [mcp-helper] op=exit ... code= signal= lifetimeMs= stderr-tail= (always)
|
|
35
|
+
*
|
|
36
|
+
* The wrapper never writes to fd 1 (stdout) — that is the JSON-RPC channel.
|
|
37
|
+
* SIGTERM/SIGINT are forwarded to the child so a Claude Code shutdown does
|
|
38
|
+
* not orphan it; the child exit code/signal is propagated verbatim.
|
|
51
39
|
*/
|
|
52
40
|
|
|
53
41
|
import { spawn } from "node:child_process";
|
|
@@ -56,31 +44,71 @@ import { resolve } from "node:path";
|
|
|
56
44
|
|
|
57
45
|
const SERVER_NAME = process.env.MCP_SPAWN_TEE_NAME ?? "unknown";
|
|
58
46
|
const LOG_DIR = process.env.LOG_DIR;
|
|
47
|
+
const SESSION_ID = process.env.SESSION_ID;
|
|
48
|
+
const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
59
49
|
const ENTRY = process.argv[2];
|
|
60
50
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
52
|
+
const spawnStamp = Date.now();
|
|
53
|
+
|
|
54
|
+
// Per-session raw + lifecycle log (Task 706). Wrapper-only. Empty SESSION_ID
|
|
55
|
+
// (enumeration spawn) → undefined, and the per-date file is the fallback.
|
|
56
|
+
const perSessionPath = LOG_DIR && SESSION_ID
|
|
57
|
+
? resolve(LOG_DIR, `mcp-${SERVER_NAME}-${SESSION_ID}.log`)
|
|
58
|
+
: undefined;
|
|
59
|
+
const perDatePath = LOG_DIR
|
|
60
|
+
? resolve(LOG_DIR, `mcp-${SERVER_NAME}-stderr-${new Date(spawnStamp).toISOString().slice(0, 10)}.log`)
|
|
61
|
+
: undefined;
|
|
62
|
+
|
|
63
|
+
// Rolling tail of child stderr for op=exit. Capped so a chatty child cannot
|
|
64
|
+
// grow the buffer without bound.
|
|
65
|
+
const TAIL_CAP = 2048;
|
|
66
|
+
let stderrTail = "";
|
|
67
|
+
let bootEmitted = false;
|
|
68
|
+
|
|
69
|
+
function appendSafe(path: string | undefined, data: string | Uint8Array): void {
|
|
70
|
+
if (!path || !LOG_DIR) return;
|
|
71
|
+
try {
|
|
72
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
73
|
+
appendFileSync(path, data);
|
|
74
|
+
} catch {
|
|
75
|
+
/* unwritable destination — never mask primary output */
|
|
74
76
|
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Best-effort mirror of a lifecycle line to server.log via the loopback
|
|
80
|
+
// log-ingest route. Fire-and-forget: the per-session file is authoritative,
|
|
81
|
+
// so a dropped POST (unreachable port, process exiting) loses nothing.
|
|
82
|
+
function postToServerLog(suffix: string, level: "info" | "error"): void {
|
|
83
|
+
if (!PLATFORM_PORT) return;
|
|
75
84
|
try {
|
|
76
|
-
|
|
85
|
+
const ctrl = new AbortController();
|
|
86
|
+
const t = setTimeout(() => ctrl.abort(), 500);
|
|
87
|
+
t.unref?.(); // never let the abort timer keep the wrapper's event loop alive
|
|
88
|
+
void fetch(`http://127.0.0.1:${PLATFORM_PORT}/api/admin/log-ingest`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "content-type": "application/json" },
|
|
91
|
+
body: JSON.stringify({ tag: "mcp-helper", level, line: suffix }),
|
|
92
|
+
signal: ctrl.signal,
|
|
93
|
+
}).then(() => clearTimeout(t)).catch(() => clearTimeout(t));
|
|
77
94
|
} catch {
|
|
78
|
-
/*
|
|
95
|
+
/* fetch threw synchronously — best-effort only */
|
|
79
96
|
}
|
|
80
97
|
}
|
|
81
98
|
|
|
99
|
+
// Emit one [mcp-helper] lifecycle line: authoritative per-session file (sync,
|
|
100
|
+
// survives process.exit), the wrapper's own stderr (journald / Claude Code
|
|
101
|
+
// visibility), and a best-effort server.log mirror. `suffix` is the line body
|
|
102
|
+
// after the tag and carries no newline (the log-ingest route rejects newlines).
|
|
103
|
+
function emitLifecycle(suffix: string, level: "info" | "error"): void {
|
|
104
|
+
const line = `[mcp-helper] ${suffix}\n`;
|
|
105
|
+
appendSafe(perSessionPath, line);
|
|
106
|
+
try { process.stderr.write(line); } catch { /* stderr closed */ }
|
|
107
|
+
postToServerLog(suffix, level);
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
if (!ENTRY) {
|
|
83
|
-
|
|
111
|
+
emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason="no entry given (argv[2] missing)"`, "error");
|
|
84
112
|
process.exit(2);
|
|
85
113
|
}
|
|
86
114
|
|
|
@@ -89,46 +117,47 @@ const child = spawn(process.execPath, [ENTRY], {
|
|
|
89
117
|
env: process.env,
|
|
90
118
|
});
|
|
91
119
|
|
|
92
|
-
|
|
120
|
+
emitLifecycle(`op=spawn session=${SESSION_ID8} server=${SERVER_NAME} pid=${child.pid ?? -1} entry=${ENTRY}`, "info");
|
|
93
121
|
|
|
94
122
|
child.on("error", (err) => {
|
|
95
123
|
const msg = err instanceof Error ? err.message : String(err);
|
|
96
|
-
|
|
124
|
+
emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason=${JSON.stringify(`spawn error: ${msg}`)}`, "error");
|
|
97
125
|
process.exit(127);
|
|
98
126
|
});
|
|
99
127
|
|
|
100
128
|
if (child.stderr) {
|
|
101
129
|
child.stderr.on("data", (chunk: Buffer) => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
process.stderr.write(chunk);
|
|
112
|
-
} catch {
|
|
113
|
-
/* stderr closed — nothing else to do */
|
|
130
|
+
appendSafe(perDatePath, chunk); // per-date raw (back-compat)
|
|
131
|
+
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
132
|
+
try { process.stderr.write(chunk); } catch { /* stderr closed */ } // passthrough
|
|
133
|
+
stderrTail = (stderrTail + chunk.toString("utf8")).slice(-TAIL_CAP);
|
|
134
|
+
if (!bootEmitted) {
|
|
135
|
+
bootEmitted = true;
|
|
136
|
+
const head = chunk.toString("utf8").split("\n")[0].slice(0, 200);
|
|
137
|
+
emitLifecycle(`op=boot session=${SESSION_ID8} server=${SERVER_NAME} head=${JSON.stringify(head)}`, "info");
|
|
114
138
|
}
|
|
115
139
|
});
|
|
116
140
|
}
|
|
117
141
|
|
|
118
|
-
|
|
119
|
-
const forward = (signal: NodeJS.Signals) => {
|
|
120
|
-
if (!child.killed) child.kill(signal);
|
|
121
|
-
};
|
|
142
|
+
const forward = (signal: NodeJS.Signals) => { if (!child.killed) child.kill(signal); };
|
|
122
143
|
process.on("SIGTERM", () => forward("SIGTERM"));
|
|
123
144
|
process.on("SIGINT", () => forward("SIGINT"));
|
|
124
145
|
|
|
146
|
+
// Signal → number, so a signal-killed child propagates as 128+signum (the
|
|
147
|
+
// shell convention). `code` is null on a signal exit, so `128 + (code ?? 0)`
|
|
148
|
+
// would collapse every signal to 128 and lose the signal identity in the
|
|
149
|
+
// wrapper's own exit status.
|
|
150
|
+
const SIGNUM: Record<string, number> = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGKILL: 9, SIGTERM: 15 };
|
|
151
|
+
|
|
125
152
|
child.on("exit", (code, signal) => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
153
|
+
const lifetimeMs = Date.now() - spawnStamp;
|
|
154
|
+
const tail = stderrTail.slice(-200);
|
|
155
|
+
const level: "info" | "error" = signal || (code ?? 0) !== 0 ? "error" : "info";
|
|
156
|
+
emitLifecycle(
|
|
157
|
+
`op=exit session=${SESSION_ID8} server=${SERVER_NAME} pid=${child.pid ?? -1} ` +
|
|
158
|
+
`code=${code ?? "—"} signal=${signal ?? "—"} lifetimeMs=${lifetimeMs} stderr-tail=${JSON.stringify(tail)}`,
|
|
159
|
+
level,
|
|
160
|
+
);
|
|
161
|
+
if (signal) process.exit(128 + (SIGNUM[signal] ?? 0));
|
|
133
162
|
process.exit(code ?? 0);
|
|
134
163
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:137ecdb03fdbb733f6150e0d931f4e5f30bf7daafcf738398d8c2768b868936e
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -145,6 +145,8 @@ Everything Maxy can do is provided by a plugin. Each plugin is a self-contained
|
|
|
145
145
|
|
|
146
146
|
**How tools and roles reach the session.** Each `claude` PTY spawn registers every plugin's MCP server and every bundled subagent directory before the operator's first turn — a per-spawn `mcp-config.json` written by the session manager and passed as `--mcp-config` on the PTY argv, plus one `--add-dir` per agents directory. Admin sessions see every plugin and every role; public sessions see only plugins with at least one public-allowlisted tool. The manager refuses to start when a plugin's `PLUGIN.md` declares tools without a matching `mcp:` block (forensic signal: `boot-failed reason=mcp-allowlist-without-server …`). See `internals.md` "Spawn-time MCP and subagent registration" for the full mechanism and `internals.md` "Tool Eagerness" for the separate ToolSearch-vs-eager registration concern.
|
|
147
147
|
|
|
148
|
+
**MCP helper death observability (Task 706).** `claude` spawns each plugin MCP helper as its own child, so the manager cannot see a helper die. Every spawn-capable plugin therefore wraps its `mcp:` command with `lib/mcp-spawn-tee`, a parent-side node wrapper that is the helper's real parent and observes its true exit code, signal, and lifetime (including SIGKILL / transport crashes). The wrapper tees the helper's stderr to a wrapper-only per-session file `~/.{brand}/logs/mcp-<server>-<sessionId>.log` — never mixed with the admin-server enumeration copy — and emits lifecycle lines keyed `session=<id8> server=<name>`: `[mcp-helper] op=spawn … pid= entry=`, `[mcp-helper] op=boot … head=<first stderr bytes>`, and `[mcp-helper] op=exit … code= signal= lifetimeMs= stderr-tail=` on every exit (including a clean one). Lines are written synchronously to the per-session file and best-effort mirrored to `server.log` via the loopback log-ingest route. Diagnose a dead helper by `grep '\[mcp-helper\]' ~/.{brand}/logs/server.log` filtered by `session=<id8>`, then read the per-session file for its last output. Full contract and diagnostic path in [`.docs/mcp-helper-observability.md`](../../../.docs/mcp-helper-observability.md). The alive-but-wedged case (no process exit) is the deferred `[mcp-helper-census]` reconciler (Task 710).
|
|
149
|
+
|
|
148
150
|
**Where premium bundle subs live.** Bundle subs (`loop`, `property-data`, `brochures`, etc. inside `real-agent`) live exclusively at `premium-plugins/<bundle>/plugins/<sub>/` and are registered via the resolver's bundle-descent walk. Standalone premiums (no `BUNDLE.md`, e.g. `writer-craft`, `teaching`, `venture-studio`) live exclusively at `premium-plugins/<name>/` and are registered via the resolver's dual-root scan (`platform/plugins/` and `premium-plugins/` are both `pluginsRoots`). Neither shape is flat-copied into `platform/plugins/<name>/`. A divergent flat copy of a bundle sub is treated as an operator override: the resolver refuses to boot with `boot-failed reason=mcp-plugin-duplicate <plugin> declared by more than one plugins root: <pathA> (sha=…) vs <pathB> (sha=…)` so the operator can `sha256sum` both paths and remove the stale one. Byte-identical bundle-sub flat copies left over from installer versions that flat-copied bundle subs are reaped on the first post-upgrade boot (`[premium-auto-deliver] reaped sub=<name> reason=duplicate-of-premium-tree`). Standalone flat copies (leaked by the pre-fix `autoDeliverPremiumPlugins` standalone branch) are reaped unconditionally — there is no documented override path for standalones at `platform/plugins/<name>/` — and the reaper logs `[premium-auto-deliver] reaped standalone=<name> matches-source=<true|false>` so divergent reaps leave a forensic trail.
|
|
149
151
|
|
|
150
152
|
Plugins are installed and managed through conversation. You can add marketplace plugins (like Stripe) or use Maxy's built-in ones (contacts, memory, Telegram).
|
|
@@ -31,6 +31,8 @@ Everything {{productName}} can do is provided by a plugin. Each plugin is a self
|
|
|
31
31
|
|
|
32
32
|
**How tools and roles reach the session.** Each `claude` PTY spawn registers every plugin's MCP server and every bundled subagent directory before the operator's first turn — a per-spawn `mcp-config.json` written by the session manager and passed as `--mcp-config` on the PTY argv, plus one `--add-dir` per agents directory. Admin sessions see every plugin and every role; public sessions see only plugins with at least one public-allowlisted tool. The manager refuses to start when a plugin's `PLUGIN.md` declares tools without a matching `mcp:` block (forensic signal: `boot-failed reason=mcp-allowlist-without-server …`). See `internals.md` "Spawn-time MCP and subagent registration" for the full mechanism and `internals.md` "Tool Eagerness" for the separate ToolSearch-vs-eager registration concern.
|
|
33
33
|
|
|
34
|
+
**MCP helper death observability (Task 706).** `claude` spawns each plugin MCP helper as its own child, so the manager cannot see a helper die. Every spawn-capable plugin therefore wraps its `mcp:` command with `lib/mcp-spawn-tee`, a parent-side node wrapper that is the helper's real parent and observes its true exit code, signal, and lifetime (including SIGKILL / transport crashes). The wrapper tees the helper's stderr to a wrapper-only per-session file `~/.{brand}/logs/mcp-<server>-<sessionId>.log` — never mixed with the admin-server enumeration copy — and emits lifecycle lines keyed `session=<id8> server=<name>`: `[mcp-helper] op=spawn … pid= entry=`, `[mcp-helper] op=boot … head=<first stderr bytes>`, and `[mcp-helper] op=exit … code= signal= lifetimeMs= stderr-tail=` on every exit (including a clean one). Lines are written synchronously to the per-session file and best-effort mirrored to `server.log` via the loopback log-ingest route. Diagnose a dead helper by `grep '\[mcp-helper\]' ~/.{brand}/logs/server.log` filtered by `session=<id8>`, then read the per-session file for its last output. Full contract and diagnostic path in [`.docs/mcp-helper-observability.md`](../../../.docs/mcp-helper-observability.md). The alive-but-wedged case (no process exit) is the deferred `[mcp-helper-census]` reconciler (Task 710).
|
|
35
|
+
|
|
34
36
|
**Where premium bundle subs live.** Bundle subs (`loop`, `property-data`, `brochures`, etc. inside `real-agent`) live exclusively at `premium-plugins/<bundle>/plugins/<sub>/` and are registered via the resolver's bundle-descent walk. Standalone premiums (no `BUNDLE.md`, e.g. `writer-craft`, `teaching`, `venture-studio`) live exclusively at `premium-plugins/<name>/` and are registered via the resolver's dual-root scan (`platform/plugins/` and `premium-plugins/` are both `pluginsRoots`). Neither shape is flat-copied into `platform/plugins/<name>/`. A divergent flat copy of a bundle sub is treated as an operator override: the resolver refuses to boot with `boot-failed reason=mcp-plugin-duplicate <plugin> declared by more than one plugins root: <pathA> (sha=…) vs <pathB> (sha=…)` so the operator can `sha256sum` both paths and remove the stale one. Byte-identical bundle-sub flat copies left over from installer versions that flat-copied bundle subs are reaped on the first post-upgrade boot (`[premium-auto-deliver] reaped sub=<name> reason=duplicate-of-premium-tree`). Standalone flat copies (leaked by the pre-fix `autoDeliverPremiumPlugins` standalone branch) are reaped unconditionally — there is no documented override path for standalones at `platform/plugins/<name>/` — and the reaper logs `[premium-auto-deliver] reaped standalone=<name> matches-source=<true|false>` so divergent reaps leave a forensic trail.
|
|
35
37
|
|
|
36
38
|
Plugins are installed and managed through conversation. You can add marketplace plugins (like Stripe) or use {{productName}}'s built-in ones (contacts, memory, Telegram).
|
|
@@ -58,7 +58,7 @@ Manages the agent's own dedicated email account — IMAP for reading, SMTP for s
|
|
|
58
58
|
|
|
59
59
|
**Identity boundary (operator-identity fix).** This plugin manages the AGENT's email channel: an `:EmailAccount` node with IMAP/SMTP credentials, polling state, and the agent's `agentAddress`. It is NOT the operator-identity store. The operator's own email lives on the OWNS-bound `:Person` (`Person.email`) and is captured via `profile-update` with `personFields`. Confusing the two surfaces (writing the operator's personal email to `EmailAccount.agentAddress`, or vice versa) breaks routing AND identity coverage. See [.docs/agents.md § Coverage-driven elicitation bias] for the operator-identity contract.
|
|
60
60
|
|
|
61
|
-
**Login vs alias — the default recipient set.** A config carries two addresses. `email` is the IMAP login: the address you authenticate as, and an address this mailbox owns. `agentAddress` is an *additional* alias to surface (often a per-agent address on a shared catch-all mailbox). When they differ, `email-read`, `email-search`,
|
|
61
|
+
**Login vs alias — the default recipient set.** A config carries two addresses. `email` is the IMAP login: the address you authenticate as, and an address this mailbox owns. `agentAddress` is an *additional* alias to surface (often a per-agent address on a shared catch-all mailbox). When they differ, `email-read`, `email-search`, `email-otp-extract`, and `email-fetch` default to surfacing mail whose **To or Cc** carries **either** address — not the alias alone — because replies commonly arrive at the login address, and a reply or multi-party thread routinely Cc's the alias rather than To'ing it (a proposal sent from `info@` is answered to `info@`, not the `maxy@` alias; a thread copies `maxy@` on Cc). When no distinct alias is configured, no recipient filter is applied. Pass an explicit `to=` to narrow to a single address. Whenever the default filter hides messages from the fetched window, `email-read`/`email-search` append a `Recipient filter: kept N, hid M (to ∈ {…}); pass to="…" to change.` line — including when every message was hidden — so "I can't see it" is never indistinguishable from "it isn't there". `email-fetch` has no tool-output filter line; its kept-vs-hidden split is recorded in the stderr `diag("fetchSinceUid", "filter applied", …)` log instead.
|
|
62
62
|
|
|
63
63
|
## Capabilities
|
|
64
64
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-resolve.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/attachment-resolve.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { resolveOutboundAttachments, accountDirFor } from '../lib/attachment-resolve.js';
|
|
6
|
+
describe('Task 714 — attachment-resolve account-dir resolution', () => {
|
|
7
|
+
let root = ''; // stands in for dirname(PLATFORM_ROOT)
|
|
8
|
+
let platformRoot = '';
|
|
9
|
+
const accountId = 'acc-1';
|
|
10
|
+
let accountDir = '';
|
|
11
|
+
let inFile = '';
|
|
12
|
+
const saved = { ...process.env };
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// realpath up front so realpathSync() inside the resolver (which canonicalizes
|
|
15
|
+
// /var → /private/var on macOS) matches the paths we assert against.
|
|
16
|
+
root = realpathSync(mkdtempSync(join(tmpdir(), 't714-')));
|
|
17
|
+
platformRoot = join(root, 'platform');
|
|
18
|
+
mkdirSync(platformRoot, { recursive: true });
|
|
19
|
+
accountDir = join(root, 'data', 'accounts', accountId);
|
|
20
|
+
mkdirSync(accountDir, { recursive: true });
|
|
21
|
+
inFile = join(accountDir, 'doc.pdf');
|
|
22
|
+
writeFileSync(inFile, 'x');
|
|
23
|
+
delete process.env.ACCOUNT_DIR;
|
|
24
|
+
delete process.env.PLATFORM_ROOT;
|
|
25
|
+
delete process.env.ACCOUNT_ID;
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
process.env = { ...saved };
|
|
29
|
+
if (root)
|
|
30
|
+
rmSync(root, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
it('accountDirFor derives PLATFORM_ROOT/../data/accounts/<id>', () => {
|
|
33
|
+
process.env.PLATFORM_ROOT = platformRoot;
|
|
34
|
+
expect(accountDirFor(accountId)).toBe(accountDir);
|
|
35
|
+
});
|
|
36
|
+
it('accountDirFor returns undefined when PLATFORM_ROOT is absent', () => {
|
|
37
|
+
expect(accountDirFor(accountId)).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
it('explicit accountDir validates an in-account file', () => {
|
|
40
|
+
const out = resolveOutboundAttachments(accountDir, [inFile]);
|
|
41
|
+
expect(out).toHaveLength(1);
|
|
42
|
+
expect(out[0].path).toBe(inFile);
|
|
43
|
+
});
|
|
44
|
+
it('falls back to PLATFORM_ROOT+ACCOUNT_ID when ACCOUNT_DIR is unset', () => {
|
|
45
|
+
process.env.PLATFORM_ROOT = platformRoot;
|
|
46
|
+
process.env.ACCOUNT_ID = accountId;
|
|
47
|
+
const out = resolveOutboundAttachments(undefined, [inFile]);
|
|
48
|
+
expect(out).toHaveLength(1);
|
|
49
|
+
});
|
|
50
|
+
it('uses ACCOUNT_DIR directly when present', () => {
|
|
51
|
+
process.env.ACCOUNT_DIR = accountDir;
|
|
52
|
+
const out = resolveOutboundAttachments(undefined, [inFile]);
|
|
53
|
+
expect(out).toHaveLength(1);
|
|
54
|
+
});
|
|
55
|
+
it('throws a precise error when neither source resolves', () => {
|
|
56
|
+
expect(() => resolveOutboundAttachments(undefined, [inFile])).toThrow(/no account context/);
|
|
57
|
+
});
|
|
58
|
+
it('still rejects a path outside the account directory', () => {
|
|
59
|
+
const outside = join(root, 'outside.pdf');
|
|
60
|
+
writeFileSync(outside, 'x');
|
|
61
|
+
expect(() => resolveOutboundAttachments(accountDir, [outside])).toThrow(/outside the account directory/);
|
|
62
|
+
});
|
|
63
|
+
it('returns [] for empty paths without needing any account context', () => {
|
|
64
|
+
expect(resolveOutboundAttachments(undefined, [])).toEqual([]);
|
|
65
|
+
expect(resolveOutboundAttachments(undefined, undefined)).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
//# sourceMappingURL=attachment-resolve.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-resolve.test.js","sourceRoot":"","sources":["../../src/__tests__/attachment-resolve.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-setup.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/email-setup.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
// Mock the MX surface so an M365 custom domain can be driven deterministically,
|
|
3
|
+
// and the heavy IMAP/SMTP/credentials deps so emailSetup is exercised in
|
|
4
|
+
// isolation. The Microsoft short-circuit must return BEFORE any of these run.
|
|
5
|
+
const { resolveMx } = vi.hoisted(() => ({ resolveMx: vi.fn() }));
|
|
6
|
+
vi.mock("node:dns/promises", () => ({ resolveMx }));
|
|
7
|
+
const imap = vi.hoisted(() => ({ testConnection: vi.fn(), getInboxCount: vi.fn() }));
|
|
8
|
+
const smtp = vi.hoisted(() => ({ testConnection: vi.fn() }));
|
|
9
|
+
vi.mock("../lib/imap.js", () => imap);
|
|
10
|
+
vi.mock("../lib/smtp.js", () => smtp);
|
|
11
|
+
vi.mock("../lib/credentials.js", () => ({
|
|
12
|
+
writeConfig: vi.fn(),
|
|
13
|
+
writePassword: vi.fn(),
|
|
14
|
+
removeConfig: vi.fn(),
|
|
15
|
+
checkAddressBinding: vi.fn().mockResolvedValue(null),
|
|
16
|
+
writeBindingConfig: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
const { emailSetup } = await import("../tools/email-setup.js");
|
|
19
|
+
describe("email-setup short-circuits Microsoft addresses", () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
resolveMx.mockReset();
|
|
22
|
+
imap.testConnection.mockReset();
|
|
23
|
+
smtp.testConnection.mockReset();
|
|
24
|
+
});
|
|
25
|
+
it("refuses a static Microsoft address without attempting a connection", async () => {
|
|
26
|
+
const result = await emailSetup({
|
|
27
|
+
email: "agent@outlook.com",
|
|
28
|
+
password: "irrelevant",
|
|
29
|
+
accountId: "acct-1",
|
|
30
|
+
});
|
|
31
|
+
expect(result.success).toBe(false);
|
|
32
|
+
expect(result.message).toMatch(/outlook-account-register/);
|
|
33
|
+
expect(result.message).toMatch(/outlook plugin/i);
|
|
34
|
+
expect(imap.testConnection).not.toHaveBeenCalled();
|
|
35
|
+
expect(smtp.testConnection).not.toHaveBeenCalled();
|
|
36
|
+
expect(resolveMx).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
it("refuses an M365 custom domain detected by MX, even with hosts supplied", async () => {
|
|
39
|
+
resolveMx.mockResolvedValue([
|
|
40
|
+
{ exchange: "contoso-com.mail.protection.outlook.com", priority: 0 },
|
|
41
|
+
]);
|
|
42
|
+
const result = await emailSetup({
|
|
43
|
+
email: "ceo@contoso.com",
|
|
44
|
+
password: "irrelevant",
|
|
45
|
+
imapHost: "outlook.office365.com",
|
|
46
|
+
smtpHost: "smtp.office365.com",
|
|
47
|
+
accountId: "acct-1",
|
|
48
|
+
});
|
|
49
|
+
expect(result.success).toBe(false);
|
|
50
|
+
expect(result.message).toMatch(/outlook-account-register/);
|
|
51
|
+
expect(imap.testConnection).not.toHaveBeenCalled();
|
|
52
|
+
expect(smtp.testConnection).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=email-setup.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-setup.test.js","sourceRoot":"","sources":["../../src/__tests__/email-setup.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/providers.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
const { resolveMx } = vi.hoisted(() => ({ resolveMx: vi.fn() }));
|
|
3
|
+
vi.mock("node:dns/promises", () => ({ resolveMx }));
|
|
4
|
+
// Import AFTER the mock is registered.
|
|
5
|
+
const { describeProvider, inferProvider, getProviderNotes } = await import("../lib/providers.js");
|
|
6
|
+
describe("provider MX fallback", () => {
|
|
7
|
+
beforeEach(() => resolveMx.mockReset());
|
|
8
|
+
it("static-map domains resolve without an MX lookup", async () => {
|
|
9
|
+
const info = await describeProvider("someone@gmail.com");
|
|
10
|
+
expect(info.status).toBe("known");
|
|
11
|
+
if (info.status === "known")
|
|
12
|
+
expect(info.defaults.imapHost).toBe("imap.gmail.com");
|
|
13
|
+
expect(resolveMx).not.toHaveBeenCalled();
|
|
14
|
+
});
|
|
15
|
+
it("a Google-hosted custom domain resolves to the gmail entry via MX", async () => {
|
|
16
|
+
resolveMx.mockResolvedValue([
|
|
17
|
+
{ exchange: "aspmx.l.google.com", priority: 1 },
|
|
18
|
+
{ exchange: "alt1.aspmx.l.google.com", priority: 5 },
|
|
19
|
+
]);
|
|
20
|
+
const info = await describeProvider("dale@glsmithandsons.co.uk");
|
|
21
|
+
expect(info.status).toBe("known");
|
|
22
|
+
if (info.status === "known") {
|
|
23
|
+
expect(info.defaults.imapHost).toBe("imap.gmail.com");
|
|
24
|
+
expect(info.defaults.smtpHost).toBe("smtp.gmail.com");
|
|
25
|
+
expect(info.notes).toMatch(/app/i);
|
|
26
|
+
}
|
|
27
|
+
expect(resolveMx).toHaveBeenCalledWith("glsmithandsons.co.uk");
|
|
28
|
+
});
|
|
29
|
+
it("inferProvider routes a Google-MX custom domain to gmail hosts", async () => {
|
|
30
|
+
resolveMx.mockResolvedValue([{ exchange: "ASPMX.L.GOOGLE.COM.", priority: 1 }]);
|
|
31
|
+
const d = await inferProvider("x@glsmithandsons.co.uk");
|
|
32
|
+
expect(d?.imapHost).toBe("imap.gmail.com");
|
|
33
|
+
});
|
|
34
|
+
it("getProviderNotes returns the gmail app-password note for a Google-MX domain", async () => {
|
|
35
|
+
resolveMx.mockResolvedValue([{ exchange: "aspmx.l.google.com", priority: 1 }]);
|
|
36
|
+
const notes = await getProviderNotes("x@glsmithandsons.co.uk");
|
|
37
|
+
expect(notes).toMatch(/app password/i);
|
|
38
|
+
});
|
|
39
|
+
it("a non-Google MX domain stays unknown", async () => {
|
|
40
|
+
resolveMx.mockResolvedValue([{ exchange: "mx.lcn.com", priority: 10 }]);
|
|
41
|
+
const info = await describeProvider("dale@somethingelse.co.uk");
|
|
42
|
+
expect(info.status).toBe("unknown");
|
|
43
|
+
});
|
|
44
|
+
it("an MX resolution failure falls back to unknown", async () => {
|
|
45
|
+
// A malformed resolveMx result drives resolveByMx into the same catch
|
|
46
|
+
// branch a real DNS rejection (NXDOMAIN/SERVFAIL) takes — both end in
|
|
47
|
+
// the `[email] mx-lookup ... outcome=error` line and an `unknown`
|
|
48
|
+
// fallback. The error is thrown inside resolveByMx (records.map on a
|
|
49
|
+
// non-array), not by the spy itself, so vitest does not surface a
|
|
50
|
+
// separately-tracked spy rejection.
|
|
51
|
+
resolveMx.mockResolvedValue(null);
|
|
52
|
+
const info = await describeProvider("nobody@no-such-domain-xyz.test");
|
|
53
|
+
expect(info.status).toBe("unknown");
|
|
54
|
+
});
|
|
55
|
+
it("an unsupported domain never triggers an MX lookup", async () => {
|
|
56
|
+
const info = await describeProvider("user@tuta.com");
|
|
57
|
+
expect(info.status).toBe("unsupported");
|
|
58
|
+
expect(resolveMx).not.toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("Microsoft → outlook Graph plugin redirect", () => {
|
|
62
|
+
beforeEach(() => resolveMx.mockReset());
|
|
63
|
+
it("a static Microsoft domain redirects to the outlook plugin without an MX lookup", async () => {
|
|
64
|
+
const info = await describeProvider("someone@outlook.com");
|
|
65
|
+
expect(info.status).toBe("redirect");
|
|
66
|
+
if (info.status === "redirect") {
|
|
67
|
+
expect(info.plugin).toBe("outlook");
|
|
68
|
+
expect(info.registerTool).toBe("outlook-account-register");
|
|
69
|
+
// The redirect reason carries the retired-auth wording (also the
|
|
70
|
+
// in-source outlook note — one shared constant).
|
|
71
|
+
expect(info.reason).toMatch(/1 March 2026/);
|
|
72
|
+
expect(info.reason).toMatch(/basic auth/i);
|
|
73
|
+
expect(info.reason).not.toMatch(/app password if 2FA/);
|
|
74
|
+
}
|
|
75
|
+
expect(resolveMx).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
it("the hotmail.co.uk alias also redirects", async () => {
|
|
78
|
+
const info = await describeProvider("user@hotmail.co.uk");
|
|
79
|
+
expect(info.status).toBe("redirect");
|
|
80
|
+
expect(resolveMx).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
it("an M365 custom domain redirects via MX (mail.protection.outlook.com)", async () => {
|
|
83
|
+
resolveMx.mockResolvedValue([
|
|
84
|
+
{ exchange: "contoso-com.mail.protection.outlook.com", priority: 0 },
|
|
85
|
+
]);
|
|
86
|
+
const info = await describeProvider("ceo@contoso.com");
|
|
87
|
+
expect(info.status).toBe("redirect");
|
|
88
|
+
if (info.status === "redirect") {
|
|
89
|
+
expect(info.plugin).toBe("outlook");
|
|
90
|
+
expect(info.registerTool).toBe("outlook-account-register");
|
|
91
|
+
}
|
|
92
|
+
expect(resolveMx).toHaveBeenCalledWith("contoso.com");
|
|
93
|
+
});
|
|
94
|
+
it("inferProvider returns null for a Microsoft address (no usable IMAP defaults)", async () => {
|
|
95
|
+
const d = await inferProvider("someone@outlook.com");
|
|
96
|
+
expect(d).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("gmail notes currency", () => {
|
|
100
|
+
it("requires an app password and drops the stale enable-IMAP line", async () => {
|
|
101
|
+
const info = await describeProvider("someone@gmail.com");
|
|
102
|
+
if (info.status !== "known")
|
|
103
|
+
throw new Error("expected known");
|
|
104
|
+
const notes = info.defaults.notes ?? "";
|
|
105
|
+
expect(notes).toMatch(/app password/i);
|
|
106
|
+
expect(notes).toMatch(/2-step verification/i);
|
|
107
|
+
expect(notes).not.toMatch(/IMAP must be enabled in Gmail settings/i);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=providers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.test.js","sourceRoot":"","sources":["../../src/__tests__/providers.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipient-filter.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/recipient-filter.test.ts"],"names":[],"mappings":""}
|