@rubytech/create-maxy-code 0.1.295 → 0.1.297
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/models/dist/index.d.ts +1 -0
- package/payload/platform/lib/models/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/models/dist/index.js +4 -2
- package/payload/platform/lib/models/dist/index.js.map +1 -1
- package/payload/platform/lib/models/src/index.ts +3 -1
- package/payload/platform/neo4j/schema.cypher +10 -2
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +27 -0
- package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +24 -5
- package/payload/platform/plugins/admin/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/admin/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -13
- package/payload/platform/plugins/aeo/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/aeo/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/aeo/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/browser/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/browser/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/browser/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/contacts/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/contacts/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/contacts/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/docs/references/getting-started.md +1 -1
- package/payload/platform/plugins/docs/references/platform.md +1 -11
- package/payload/platform/plugins/email/.claude-plugin/plugin.json +6 -2
- package/payload/platform/plugins/email/PLUGIN.md +6 -2
- package/payload/platform/plugins/email/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/email/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft.test.js +75 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-setup.test.js +58 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-setup.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +74 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-probe.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-probe.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-probe.test.js +69 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-probe.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.js +46 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/mail-options.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/setup-probe.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/setup-probe.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/setup-probe.test.js +56 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/setup-probe.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/smtp-probe.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/smtp-probe.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/smtp-probe.test.js +46 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/smtp-probe.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/index.js +44 -0
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +16 -3
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +62 -5
- 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 +42 -0
- package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.js +72 -0
- package/payload/platform/plugins/email/mcp/dist/lib/setup-probe.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts +16 -6
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.js +37 -20
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/send-transactional.js +1 -2
- package/payload/platform/plugins/email/mcp/dist/scripts/send-transactional.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft.d.ts +13 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft.js +45 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft.js.map +1 -0
- 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 +2 -8
- 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 +25 -3
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
- package/payload/platform/plugins/email/references/email-reference.md +15 -0
- package/payload/platform/plugins/email/skills/email-composition/SKILL.md +15 -4
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/memory/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/memory/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-doc-contradictions.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-doc-contradictions.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-doc-contradictions.test.js +214 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-doc-contradictions.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-write-path-task802.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-write-path-task802.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-write-path-task802.test.js +113 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-write-path-task802.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +52 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +90 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +3 -0
- package/payload/platform/plugins/memory/references/schema-base.md +6 -2
- package/payload/platform/plugins/memory/references/schema-construction.md +8 -6
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/outlook/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/outlook/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/replicate/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/replicate/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/replicate/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/scheduling/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/scheduling/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/scheduling/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/telegram/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/telegram/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/telegram/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/url-get/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/url-get/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/url-get/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/whatsapp/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/whatsapp/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/whatsapp/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/work/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/work/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/work/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/workflows/.claude-plugin/plugin.json +5 -1
- package/payload/platform/plugins/workflows/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/platform/plugins/workflows/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/scripts/smoke-boot-services.sh +1 -1
- 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/whatsapp-channel/dist/jsonl-watch.d.ts +10 -0
- package/payload/platform/services/whatsapp-channel/dist/jsonl-watch.d.ts.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/jsonl-watch.js +70 -0
- package/payload/platform/services/whatsapp-channel/dist/jsonl-watch.js.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +10 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +15 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts +6 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +38 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/templates/account.json +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/premium-plugins/writer-craft/.claude-plugin/plugin.json +5 -1
- package/payload/premium-plugins/writer-craft/lib/mcp-spawn-tee/index.js +159 -0
- package/payload/premium-plugins/writer-craft/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/server/public/assets/AdminShell-DpjiG4KO.js +1 -0
- package/payload/server/public/assets/{Checkbox-DTrIyIFP.js → Checkbox-o5EbM1Sd.js} +1 -1
- package/payload/server/public/assets/{Transcript-C3aDVJ-n.js → Transcript-B-roO2vK.js} +1 -1
- package/payload/server/public/assets/{admin-DVdyV-Q6.js → admin-DmBG61gA.js} +1 -1
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-DR9W0q1P.js → architectureDiagram-Q4EWVU46-DCLfcVZf.js} +1 -1
- package/payload/server/public/assets/{audio-attachment-mime-DGEfApkA.js → audio-attachment-mime-D_YMUIBb.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-C3uwaI7p.js → blockDiagram-DXYQGD6D-DtTb6oGR.js} +1 -1
- package/payload/server/public/assets/{browser-DrJReVEG.js → browser-DPqQnY8B.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-C8hmUDdd.js → c4Diagram-AHTNJAMY-DHMgkbse.js} +1 -1
- package/payload/server/public/assets/channel-BGB5Q-QM.js +1 -0
- package/payload/server/public/assets/chat-BpceL2qA.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O-BRCubMCb.js → chunk-336JU56O-DQja-fc4.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-BNINkVsL.js → chunk-426QAEUC-uBZXKz4Z.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-DSMj_AZh.js → chunk-4TB4RGXK-DViPFnGL.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-6DNGaUkf.js → chunk-5FUZZQ4R-CdKJ7vdW.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-DPhkkS4p.js → chunk-5PVQY5BW-BIJiayBe.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-BjWSqn7_.js → chunk-EDXVE4YY-CDDqx-TB.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-BX1ZnU4A.js → chunk-ENJZ2VHE-BVNFvue5.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-DvYSWH7x.js → chunk-ICPOFSXX-DEZQolUW.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-7_m3mEOK.js → chunk-OYMX7WX6-Dn0reHna.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-Bl1a895I.js → chunk-U2HBQHQK-sqMpBHaS.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-WkSpBPYx.js → chunk-X2U36JSP-ByhTOa14.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-DpWRmPUo.js → chunk-YZCP3GAM-CGo_et0d.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-D9Dgmeso.js → chunk-ZZ45TVLE-B9aPwq_o.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DdUR3hKH.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-B0ZE2WR0.js +1 -0
- package/payload/server/public/assets/clone-B5aO4uPo.js +1 -0
- package/payload/server/public/assets/{dagre-PrVfCcSt.js → dagre-DE88U4Vk.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-DPq3dcLH.js → dagre-KV5264BT-oeoQWi1f.js} +1 -1
- package/payload/server/public/assets/data-6mvcS2_6.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-DoszElKb.js → diagram-5BDNPKRD-HmWmPhlY.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-BRuqosix.js → diagram-G4DWMVQ6-Bkao4d6n.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-CpigG1d_.js → diagram-MMDJMWI5-Eh2fnSb-.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-BS0eYiq9.js → diagram-TYMM5635-C8705_ZE.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-VEQyXCFl.js → erDiagram-SMLLAGMA-Bkhe0ZPv.js} +1 -1
- package/payload/server/public/assets/{file-download-C3pBZjt9.js → file-download-DVUMxBgB.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-2NYMfqHX.js → flowDiagram-DWJPFMVM-rQKH0HF0.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-AAAa2sqE.js → ganttDiagram-T4ZO3ILL-C3oSBbAe.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-d8NUAIz5.js → gitGraphDiagram-UUTBAWPF-28GEOzIR.js} +1 -1
- package/payload/server/public/assets/{graph-Bx9_2qbJ.js → graph-B5R9qDya.js} +1 -1
- package/payload/server/public/assets/{graph-labels-Cc1nU1_f.js → graph-labels-Bcq_R9X9.js} +1 -1
- package/payload/server/public/assets/{graphlib-DUlqr2mr.js → graphlib-DhAwTSya.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-D4dh4-0g.js → infoDiagram-42DDH7IO-DnSzN29F.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DPTmm2IE.js → ishikawaDiagram-UXIWVN3A-B0mYhBS0.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-B85vjqkE.js → journeyDiagram-VCZTEJTY-CIcZskca.js} +1 -1
- package/payload/server/public/assets/{jsx-runtime-D3PZpOAF.css → jsx-runtime-DqAwfhuD.css} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-C2Lfb5Xh.js → kanban-definition-6JOO6SKY-DWEpWX0D.js} +1 -1
- package/payload/server/public/assets/{line-CODKFpwO.js → line-D6evlE4c.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-DYdFZnXH.js → mermaid-parser.core-DyRhDebz.js} +1 -1
- package/payload/server/public/assets/{mermaid.core-Bamjzahu.js → mermaid.core-DaD391Xr.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CLK90bKJ.js → mindmap-definition-QFDTVHPH-D7IMib-k.js} +1 -1
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-DEsnxhqP.js → pieDiagram-DEJITSTG-CyOSJozN.js} +1 -1
- package/payload/server/public/assets/{public-BFMu-jux.js → public-Cj7vjoG1.js} +5 -5
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-uIP9V2jZ.js → quadrantDiagram-34T5L4WZ-CHWWRW-7.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-CVS6t5tv.js → requirementDiagram-MS252O5E-x4-mHKrQ.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-BIsmtyZA.js → sankeyDiagram-XADWPNL6-DvKrAe78.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-Cu8TfQiW.js → sequenceDiagram-FGHM5R23-DW778VnP.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-ax4vYGft.js → stateDiagram-FHFEXIEX-DVnPPuE8.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-B3nWH_hq.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-3l7ojkMA.js → timeline-definition-GMOUNBTQ-CO4Sp85K.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-C_EBRd2k.js → useSelectionMode-BK4pznA6.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-C_sv0qMD.js → vennDiagram-DHZGUBPP-OCcztoT8.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-C7u4IHMB.js → wardleyDiagram-NUSXRM2D-C_1o3jux.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-A-uLXr1a.js → xychartDiagram-5P7HB3ND-BPJxl9U1.js} +1 -1
- package/payload/server/public/browser.html +6 -6
- package/payload/server/public/chat.html +8 -8
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +6 -6
- package/payload/server/public/whatsapp-wallpaper.webp +0 -0
- package/payload/server/server.js +6 -2
- package/payload/server/public/assets/AdminShell-DDieHnzi.js +0 -1
- package/payload/server/public/assets/channel-drVqp11B.js +0 -1
- package/payload/server/public/assets/chat-DeEYwIKE.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-Dplkw_pj.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-sJXv1Ihp.js +0 -1
- package/payload/server/public/assets/clone-Q_B6oO0c.js +0 -1
- package/payload/server/public/assets/data-CYyR2m_B.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BRv1By5g.js +0 -1
- /package/payload/server/public/assets/{jsx-runtime-5auA0FEj.js → jsx-runtime-CynkhuPm.js} +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* MCP spawn-tee — parent-side stderr capture + lifecycle observability.
|
|
5
|
+
*
|
|
6
|
+
* Claude Code spawns each MCP server itself; the platform never holds a
|
|
7
|
+
* ChildProcess handle. This wrapper sits between Claude Code and the real
|
|
8
|
+
* MCP server: Claude Code runs `node <this> <real-entry>`, and the wrapper
|
|
9
|
+
* spawns the real entry with `stdio:['inherit','inherit','pipe']` so stdin
|
|
10
|
+
* and stdout (the JSON-RPC channel) pass through byte-identical. Only stderr
|
|
11
|
+
* is intercepted, and only for logging.
|
|
12
|
+
*
|
|
13
|
+
* Because the wrapper IS the helper's parent, it observes the helper's true
|
|
14
|
+
* exit code, signal, and lifetime for every death mode — including a SIGKILL
|
|
15
|
+
* or a transport-layer crash that an in-process handler would miss.
|
|
16
|
+
*
|
|
17
|
+
* Claude Code CLI → wrapper (this file) → child = node <real-entry>
|
|
18
|
+
* child stdin/stdout : inherited (Claude Code pipe, untouched)
|
|
19
|
+
* child stderr : piped → per-date log + per-session log + passthrough
|
|
20
|
+
*
|
|
21
|
+
* Destinations (Task 706):
|
|
22
|
+
* - `${LOG_DIR}/mcp-<name>-stderr-<date>.log` — per-date raw (back-compat;
|
|
23
|
+
* the in-process mcp-stderr-tee and the [mcp-init-error] probe read it).
|
|
24
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — per-session raw + lifecycle
|
|
25
|
+
* lines; wrapper-only, so it never mixes the enumeration copy with the
|
|
26
|
+
* live copy. Authoritative sink. Absent when SESSION_ID is unset
|
|
27
|
+
* (enumeration spawn) — then the per-date file is the fallback.
|
|
28
|
+
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
29
|
+
* the [mcp-helper] lifecycle lines. A dropped POST loses nothing because
|
|
30
|
+
* the per-session file already holds the line (written synchronously).
|
|
31
|
+
*
|
|
32
|
+
* Lifecycle lines, correlation key `session=<id8> server=<name>`:
|
|
33
|
+
* [mcp-helper] op=spawn ... pid= entry=
|
|
34
|
+
* [mcp-helper] op=boot ... head=<first stderr bytes>
|
|
35
|
+
* [mcp-helper] op=exit ... code= signal= lifetimeMs= stderr-tail= (always)
|
|
36
|
+
*
|
|
37
|
+
* The wrapper never writes to fd 1 (stdout) — that is the JSON-RPC channel.
|
|
38
|
+
* SIGTERM/SIGINT are forwarded to the child so a Claude Code shutdown does
|
|
39
|
+
* not orphan it; the child exit code/signal is propagated verbatim.
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
const node_child_process_1 = require("node:child_process");
|
|
43
|
+
const node_fs_1 = require("node:fs");
|
|
44
|
+
const node_path_1 = require("node:path");
|
|
45
|
+
const SERVER_NAME = process.env.MCP_SPAWN_TEE_NAME ?? "unknown";
|
|
46
|
+
const LOG_DIR = process.env.LOG_DIR;
|
|
47
|
+
const SESSION_ID = process.env.SESSION_ID;
|
|
48
|
+
const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
49
|
+
const ENTRY = process.argv[2];
|
|
50
|
+
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
51
|
+
const spawnStamp = Date.now();
|
|
52
|
+
// Per-session raw + lifecycle log (Task 706). Wrapper-only. Empty SESSION_ID
|
|
53
|
+
// (enumeration spawn) → undefined, and the per-date file is the fallback.
|
|
54
|
+
const perSessionPath = LOG_DIR && SESSION_ID
|
|
55
|
+
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-${SESSION_ID}.log`)
|
|
56
|
+
: undefined;
|
|
57
|
+
const perDatePath = LOG_DIR
|
|
58
|
+
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-stderr-${new Date(spawnStamp).toISOString().slice(0, 10)}.log`)
|
|
59
|
+
: undefined;
|
|
60
|
+
// Rolling tail of child stderr for op=exit. Capped so a chatty child cannot
|
|
61
|
+
// grow the buffer without bound.
|
|
62
|
+
const TAIL_CAP = 2048;
|
|
63
|
+
let stderrTail = "";
|
|
64
|
+
let bootEmitted = false;
|
|
65
|
+
function appendSafe(path, data) {
|
|
66
|
+
if (!path || !LOG_DIR)
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
(0, node_fs_1.mkdirSync)(LOG_DIR, { recursive: true });
|
|
70
|
+
(0, node_fs_1.appendFileSync)(path, data);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* unwritable destination — never mask primary output */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Best-effort mirror of a lifecycle line to server.log via the loopback
|
|
77
|
+
// log-ingest route. Fire-and-forget: the per-session file is authoritative,
|
|
78
|
+
// so a dropped POST (unreachable port, process exiting) loses nothing.
|
|
79
|
+
function postToServerLog(suffix, level) {
|
|
80
|
+
if (!PLATFORM_PORT)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
const ctrl = new AbortController();
|
|
84
|
+
const t = setTimeout(() => ctrl.abort(), 500);
|
|
85
|
+
t.unref?.(); // never let the abort timer keep the wrapper's event loop alive
|
|
86
|
+
void fetch(`http://127.0.0.1:${PLATFORM_PORT}/api/admin/log-ingest`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: { "content-type": "application/json" },
|
|
89
|
+
body: JSON.stringify({ tag: "mcp-helper", level, line: suffix }),
|
|
90
|
+
signal: ctrl.signal,
|
|
91
|
+
}).then(() => clearTimeout(t)).catch(() => clearTimeout(t));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* fetch threw synchronously — best-effort only */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Emit one [mcp-helper] lifecycle line: authoritative per-session file (sync,
|
|
98
|
+
// survives process.exit), the wrapper's own stderr (journald / Claude Code
|
|
99
|
+
// visibility), and a best-effort server.log mirror. `suffix` is the line body
|
|
100
|
+
// after the tag and carries no newline (the log-ingest route rejects newlines).
|
|
101
|
+
function emitLifecycle(suffix, level) {
|
|
102
|
+
const line = `[mcp-helper] ${suffix}\n`;
|
|
103
|
+
appendSafe(perSessionPath, line);
|
|
104
|
+
try {
|
|
105
|
+
process.stderr.write(line);
|
|
106
|
+
}
|
|
107
|
+
catch { /* stderr closed */ }
|
|
108
|
+
postToServerLog(suffix, level);
|
|
109
|
+
}
|
|
110
|
+
if (!ENTRY) {
|
|
111
|
+
emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason="no entry given (argv[2] missing)"`, "error");
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [ENTRY], {
|
|
115
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
116
|
+
env: process.env,
|
|
117
|
+
});
|
|
118
|
+
emitLifecycle(`op=spawn session=${SESSION_ID8} server=${SERVER_NAME} pid=${child.pid ?? -1} entry=${ENTRY}`, "info");
|
|
119
|
+
child.on("error", (err) => {
|
|
120
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
121
|
+
emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason=${JSON.stringify(`spawn error: ${msg}`)}`, "error");
|
|
122
|
+
process.exit(127);
|
|
123
|
+
});
|
|
124
|
+
if (child.stderr) {
|
|
125
|
+
child.stderr.on("data", (chunk) => {
|
|
126
|
+
appendSafe(perDatePath, chunk); // per-date raw (back-compat)
|
|
127
|
+
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
128
|
+
try {
|
|
129
|
+
process.stderr.write(chunk);
|
|
130
|
+
}
|
|
131
|
+
catch { /* stderr closed */ } // passthrough
|
|
132
|
+
stderrTail = (stderrTail + chunk.toString("utf8")).slice(-TAIL_CAP);
|
|
133
|
+
if (!bootEmitted) {
|
|
134
|
+
bootEmitted = true;
|
|
135
|
+
const head = chunk.toString("utf8").split("\n")[0].slice(0, 200);
|
|
136
|
+
emitLifecycle(`op=boot session=${SESSION_ID8} server=${SERVER_NAME} head=${JSON.stringify(head)}`, "info");
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const forward = (signal) => { if (!child.killed)
|
|
141
|
+
child.kill(signal); };
|
|
142
|
+
process.on("SIGTERM", () => forward("SIGTERM"));
|
|
143
|
+
process.on("SIGINT", () => forward("SIGINT"));
|
|
144
|
+
// Signal → number, so a signal-killed child propagates as 128+signum (the
|
|
145
|
+
// shell convention). `code` is null on a signal exit, so `128 + (code ?? 0)`
|
|
146
|
+
// would collapse every signal to 128 and lose the signal identity in the
|
|
147
|
+
// wrapper's own exit status.
|
|
148
|
+
const SIGNUM = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGKILL: 9, SIGTERM: 15 };
|
|
149
|
+
child.on("exit", (code, signal) => {
|
|
150
|
+
const lifetimeMs = Date.now() - spawnStamp;
|
|
151
|
+
const tail = stderrTail.slice(-200);
|
|
152
|
+
const level = signal || (code ?? 0) !== 0 ? "error" : "info";
|
|
153
|
+
emitLifecycle(`op=exit session=${SESSION_ID8} server=${SERVER_NAME} pid=${child.pid ?? -1} ` +
|
|
154
|
+
`code=${code ?? "—"} signal=${signal ?? "—"} lifetimeMs=${lifetimeMs} stderr-tail=${JSON.stringify(tail)}`, level);
|
|
155
|
+
if (signal)
|
|
156
|
+
process.exit(128 + (SIGNUM[signal] ?? 0));
|
|
157
|
+
process.exit(code ?? 0);
|
|
158
|
+
});
|
|
159
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* MCP spawn-tee — parent-side stderr capture + lifecycle observability.
|
|
5
|
+
*
|
|
6
|
+
* Claude Code spawns each MCP server itself; the platform never holds a
|
|
7
|
+
* ChildProcess handle. This wrapper sits between Claude Code and the real
|
|
8
|
+
* MCP server: Claude Code runs `node <this> <real-entry>`, and the wrapper
|
|
9
|
+
* spawns the real entry with `stdio:['inherit','inherit','pipe']` so stdin
|
|
10
|
+
* and stdout (the JSON-RPC channel) pass through byte-identical. Only stderr
|
|
11
|
+
* is intercepted, and only for logging.
|
|
12
|
+
*
|
|
13
|
+
* Because the wrapper IS the helper's parent, it observes the helper's true
|
|
14
|
+
* exit code, signal, and lifetime for every death mode — including a SIGKILL
|
|
15
|
+
* or a transport-layer crash that an in-process handler would miss.
|
|
16
|
+
*
|
|
17
|
+
* Claude Code CLI → wrapper (this file) → child = node <real-entry>
|
|
18
|
+
* child stdin/stdout : inherited (Claude Code pipe, untouched)
|
|
19
|
+
* child stderr : piped → per-date log + per-session log + passthrough
|
|
20
|
+
*
|
|
21
|
+
* Destinations (Task 706):
|
|
22
|
+
* - `${LOG_DIR}/mcp-<name>-stderr-<date>.log` — per-date raw (back-compat;
|
|
23
|
+
* the in-process mcp-stderr-tee and the [mcp-init-error] probe read it).
|
|
24
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — per-session raw + lifecycle
|
|
25
|
+
* lines; wrapper-only, so it never mixes the enumeration copy with the
|
|
26
|
+
* live copy. Authoritative sink. Absent when SESSION_ID is unset
|
|
27
|
+
* (enumeration spawn) — then the per-date file is the fallback.
|
|
28
|
+
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
29
|
+
* the [mcp-helper] lifecycle lines. A dropped POST loses nothing because
|
|
30
|
+
* the per-session file already holds the line (written synchronously).
|
|
31
|
+
*
|
|
32
|
+
* Lifecycle lines, correlation key `session=<id8> server=<name>`:
|
|
33
|
+
* [mcp-helper] op=spawn ... pid= entry=
|
|
34
|
+
* [mcp-helper] op=boot ... head=<first stderr bytes>
|
|
35
|
+
* [mcp-helper] op=exit ... code= signal= lifetimeMs= stderr-tail= (always)
|
|
36
|
+
*
|
|
37
|
+
* The wrapper never writes to fd 1 (stdout) — that is the JSON-RPC channel.
|
|
38
|
+
* SIGTERM/SIGINT are forwarded to the child so a Claude Code shutdown does
|
|
39
|
+
* not orphan it; the child exit code/signal is propagated verbatim.
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
const node_child_process_1 = require("node:child_process");
|
|
43
|
+
const node_fs_1 = require("node:fs");
|
|
44
|
+
const node_path_1 = require("node:path");
|
|
45
|
+
const SERVER_NAME = process.env.MCP_SPAWN_TEE_NAME ?? "unknown";
|
|
46
|
+
const LOG_DIR = process.env.LOG_DIR;
|
|
47
|
+
const SESSION_ID = process.env.SESSION_ID;
|
|
48
|
+
const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
49
|
+
const ENTRY = process.argv[2];
|
|
50
|
+
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
51
|
+
const spawnStamp = Date.now();
|
|
52
|
+
// Per-session raw + lifecycle log (Task 706). Wrapper-only. Empty SESSION_ID
|
|
53
|
+
// (enumeration spawn) → undefined, and the per-date file is the fallback.
|
|
54
|
+
const perSessionPath = LOG_DIR && SESSION_ID
|
|
55
|
+
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-${SESSION_ID}.log`)
|
|
56
|
+
: undefined;
|
|
57
|
+
const perDatePath = LOG_DIR
|
|
58
|
+
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-stderr-${new Date(spawnStamp).toISOString().slice(0, 10)}.log`)
|
|
59
|
+
: undefined;
|
|
60
|
+
// Rolling tail of child stderr for op=exit. Capped so a chatty child cannot
|
|
61
|
+
// grow the buffer without bound.
|
|
62
|
+
const TAIL_CAP = 2048;
|
|
63
|
+
let stderrTail = "";
|
|
64
|
+
let bootEmitted = false;
|
|
65
|
+
function appendSafe(path, data) {
|
|
66
|
+
if (!path || !LOG_DIR)
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
(0, node_fs_1.mkdirSync)(LOG_DIR, { recursive: true });
|
|
70
|
+
(0, node_fs_1.appendFileSync)(path, data);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* unwritable destination — never mask primary output */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Best-effort mirror of a lifecycle line to server.log via the loopback
|
|
77
|
+
// log-ingest route. Fire-and-forget: the per-session file is authoritative,
|
|
78
|
+
// so a dropped POST (unreachable port, process exiting) loses nothing.
|
|
79
|
+
function postToServerLog(suffix, level) {
|
|
80
|
+
if (!PLATFORM_PORT)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
const ctrl = new AbortController();
|
|
84
|
+
const t = setTimeout(() => ctrl.abort(), 500);
|
|
85
|
+
t.unref?.(); // never let the abort timer keep the wrapper's event loop alive
|
|
86
|
+
void fetch(`http://127.0.0.1:${PLATFORM_PORT}/api/admin/log-ingest`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: { "content-type": "application/json" },
|
|
89
|
+
body: JSON.stringify({ tag: "mcp-helper", level, line: suffix }),
|
|
90
|
+
signal: ctrl.signal,
|
|
91
|
+
}).then(() => clearTimeout(t)).catch(() => clearTimeout(t));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* fetch threw synchronously — best-effort only */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Emit one [mcp-helper] lifecycle line: authoritative per-session file (sync,
|
|
98
|
+
// survives process.exit), the wrapper's own stderr (journald / Claude Code
|
|
99
|
+
// visibility), and a best-effort server.log mirror. `suffix` is the line body
|
|
100
|
+
// after the tag and carries no newline (the log-ingest route rejects newlines).
|
|
101
|
+
function emitLifecycle(suffix, level) {
|
|
102
|
+
const line = `[mcp-helper] ${suffix}\n`;
|
|
103
|
+
appendSafe(perSessionPath, line);
|
|
104
|
+
try {
|
|
105
|
+
process.stderr.write(line);
|
|
106
|
+
}
|
|
107
|
+
catch { /* stderr closed */ }
|
|
108
|
+
postToServerLog(suffix, level);
|
|
109
|
+
}
|
|
110
|
+
if (!ENTRY) {
|
|
111
|
+
emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason="no entry given (argv[2] missing)"`, "error");
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [ENTRY], {
|
|
115
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
116
|
+
env: process.env,
|
|
117
|
+
});
|
|
118
|
+
emitLifecycle(`op=spawn session=${SESSION_ID8} server=${SERVER_NAME} pid=${child.pid ?? -1} entry=${ENTRY}`, "info");
|
|
119
|
+
child.on("error", (err) => {
|
|
120
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
121
|
+
emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason=${JSON.stringify(`spawn error: ${msg}`)}`, "error");
|
|
122
|
+
process.exit(127);
|
|
123
|
+
});
|
|
124
|
+
if (child.stderr) {
|
|
125
|
+
child.stderr.on("data", (chunk) => {
|
|
126
|
+
appendSafe(perDatePath, chunk); // per-date raw (back-compat)
|
|
127
|
+
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
128
|
+
try {
|
|
129
|
+
process.stderr.write(chunk);
|
|
130
|
+
}
|
|
131
|
+
catch { /* stderr closed */ } // passthrough
|
|
132
|
+
stderrTail = (stderrTail + chunk.toString("utf8")).slice(-TAIL_CAP);
|
|
133
|
+
if (!bootEmitted) {
|
|
134
|
+
bootEmitted = true;
|
|
135
|
+
const head = chunk.toString("utf8").split("\n")[0].slice(0, 200);
|
|
136
|
+
emitLifecycle(`op=boot session=${SESSION_ID8} server=${SERVER_NAME} head=${JSON.stringify(head)}`, "info");
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const forward = (signal) => { if (!child.killed)
|
|
141
|
+
child.kill(signal); };
|
|
142
|
+
process.on("SIGTERM", () => forward("SIGTERM"));
|
|
143
|
+
process.on("SIGINT", () => forward("SIGINT"));
|
|
144
|
+
// Signal → number, so a signal-killed child propagates as 128+signum (the
|
|
145
|
+
// shell convention). `code` is null on a signal exit, so `128 + (code ?? 0)`
|
|
146
|
+
// would collapse every signal to 128 and lose the signal identity in the
|
|
147
|
+
// wrapper's own exit status.
|
|
148
|
+
const SIGNUM = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGKILL: 9, SIGTERM: 15 };
|
|
149
|
+
child.on("exit", (code, signal) => {
|
|
150
|
+
const lifetimeMs = Date.now() - spawnStamp;
|
|
151
|
+
const tail = stderrTail.slice(-200);
|
|
152
|
+
const level = signal || (code ?? 0) !== 0 ? "error" : "info";
|
|
153
|
+
emitLifecycle(`op=exit session=${SESSION_ID8} server=${SERVER_NAME} pid=${child.pid ?? -1} ` +
|
|
154
|
+
`code=${code ?? "—"} signal=${signal ?? "—"} lifetimeMs=${lifetimeMs} stderr-tail=${JSON.stringify(tail)}`, level);
|
|
155
|
+
if (signal)
|
|
156
|
+
process.exit(128 + (SIGNUM[signal] ?? 0));
|
|
157
|
+
process.exit(code ?? 0);
|
|
158
|
+
});
|
|
159
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -60,7 +60,7 @@ When the text field is empty, a microphone button appears in place of the send b
|
|
|
60
60
|
|
|
61
61
|
Voice recording requires a secure connection (HTTPS). When accessing {{productName}} over the local network via HTTP, use the tunnel URL for voice notes.
|
|
62
62
|
|
|
63
|
-
You can also drop, paste, or pick an audio file (`.opus`, `.ogg`, `.m4a`, `.mp3`, `.wav`, `.webm`) into the chat composer — for example a voice note forwarded from WhatsApp. The file is transcribed the same way the in-browser recording is, and only the transcript reaches {{productName}}; the audio itself is discarded after transcription.
|
|
63
|
+
You can also drop, paste, or pick an audio file (`.opus`, `.ogg`, `.m4a`, `.mp3`, `.wav`, `.webm`) into the chat composer — for example a voice note forwarded from WhatsApp. The file is transcribed the same way the in-browser recording is, and only the transcript reaches {{productName}}; the audio itself is discarded after transcription. If you type a message and attach an audio file on the same send, both arrive — the transcript follows your typed text.
|
|
64
64
|
|
|
65
65
|
## What {{productName}} Remembers
|
|
66
66
|
|
|
@@ -31,7 +31,7 @@ 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).
|
|
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. Both spawn paths are wrapped: the manager's per-spawn `.mcp.json` (Task 706) and the generated `.claude-plugin/plugin.json` manifests that channel sessions take helpers from (Task 807) — the manifest generator copies the tee into each `mcp-manifest: auto` plugin at `lib/mcp-spawn-tee/index.js`, because the plugin-install cache snapshot severs `${PLATFORM_ROOT}`-relative paths, and a missing tee dist at generation time is FATAL. 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
35
|
|
|
36
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.
|
|
37
37
|
|
|
@@ -171,16 +171,6 @@ Both flows run on the native Claude Code PTY surface in admin chat (Task 287). T
|
|
|
171
171
|
|
|
172
172
|
**Authorisation** is inherited from the same `canAccessAdmin()` gate that wraps every `/api/admin/*` route.
|
|
173
173
|
|
|
174
|
-
## AI Content Provenance
|
|
175
|
-
|
|
176
|
-
When your public agent sends a message to someone — via email, WhatsApp, Telegram, or SMS — the platform automatically includes a brief disclosure that the content was generated by AI. This is transparent and cannot be turned off.
|
|
177
|
-
|
|
178
|
-
- **Email:** an `X-AI-Generated` header and a footer line are added to every outbound email
|
|
179
|
-
- **WhatsApp and Telegram:** a short line is appended to the message body
|
|
180
|
-
- **SMS:** a brief suffix is appended when the message is AI-composed
|
|
181
|
-
|
|
182
|
-
Messages you write yourself (e.g. typing directly in WhatsApp) are not marked — the disclosure applies only to content composed by the AI agent.
|
|
183
|
-
|
|
184
174
|
## Session Slot Safeguards
|
|
185
175
|
|
|
186
176
|
{{productName}} runs each chat — yours and every visitor's — as a separate `claude` process on your Pi. Three safeguards keep these processes from piling up:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "email",
|
|
3
|
-
"description": "Email — dedicated email account. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox (with best-effort per-archive section hints from the graph). Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-status, email-otp-extract.",
|
|
3
|
+
"description": "Email — dedicated email account. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox (with best-effort per-archive section hints from the graph). Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search), email-draft (compose to the mailbox Drafts folder, unsent, for operator review). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-status, email-otp-extract.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
"type": "stdio",
|
|
11
11
|
"command": "node",
|
|
12
12
|
"args": [
|
|
13
|
+
"${CLAUDE_PLUGIN_ROOT}/lib/mcp-spawn-tee/index.js",
|
|
13
14
|
"${CLAUDE_PLUGIN_ROOT}/mcp/dist/index.js"
|
|
14
|
-
]
|
|
15
|
+
],
|
|
16
|
+
"env": {
|
|
17
|
+
"MCP_SPAWN_TEE_NAME": "email"
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
}
|
|
17
21
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: email
|
|
3
|
-
description: "Email — dedicated email account. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox (with best-effort per-archive section hints from the graph). Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-status, email-otp-extract."
|
|
3
|
+
description: "Email — dedicated email account. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox (with best-effort per-archive section hints from the graph). Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search), email-draft (compose to the mailbox Drafts folder, unsent, for operator review). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-status, email-otp-extract."
|
|
4
4
|
tools:
|
|
5
5
|
- name: email-provider-info
|
|
6
6
|
publicAllowlist: false
|
|
@@ -17,6 +17,9 @@ tools:
|
|
|
17
17
|
- name: email-reply
|
|
18
18
|
publicAllowlist: false
|
|
19
19
|
adminAllowlist: false
|
|
20
|
+
- name: email-draft
|
|
21
|
+
publicAllowlist: false
|
|
22
|
+
adminAllowlist: false
|
|
20
23
|
- name: email-search
|
|
21
24
|
publicAllowlist: false
|
|
22
25
|
adminAllowlist: false
|
|
@@ -68,6 +71,7 @@ Manages the agent's own dedicated email account — IMAP for reading, SMTP for s
|
|
|
68
71
|
- **Search inbox:** `email-search` — live IMAP search by sender, subject, body keyword, date range. Same per-archive hint and attachment footer as `email-read`.
|
|
69
72
|
- **Send:** `email-send` — new outbound email. Pass `cc`/`bcc` (each an array of email addresses) to copy or blind-copy recipients — CC appears in the message header, BCC recipients receive the message without appearing in any header. Pass `attachments` (absolute file paths under the account directory, ≤25 MB each) to attach files as SMTP attachments; an invalid path fails the send with no email dispatched. The success message names the CC recipients and reports the BCC count without naming them.
|
|
70
73
|
- **Reply:** `email-reply` — threaded reply to an existing email by `messageId`. Resolves the original envelope via IMAP `SEARCH HEADER Message-ID` (INBOX then `\Sent`), so threading works for any message still present on the server even if the graph never ingested it. Supports `replyAll`, `cc`/`bcc`, and `attachments` (same path rules as `email-send`). Explicit `cc`/`bcc` are added in addition to whatever `replyAll` places in `To`, passed through as given (no overlap de-duplication against `To`).
|
|
74
|
+
- **Compose to Drafts:** `email-draft` — builds the same MIME message as `email-send` (same `cc`/`bcc`/`attachments` rules; unlike a dispatched send, the stored draft keeps its `Bcc:` header, because the draft bytes are the only recipient carrier) but APPENDs it to the mailbox's `\Drafts` special-use folder with the `\Draft` flag instead of dispatching it. Nothing is sent. The success message names the Drafts folder and the APPEND UID so a saved draft is never indistinguishable from a silent no-op. Fails naming the mailbox when the server advertises no `\Drafts` special-use folder — no fallback folder is picked. Use when the operator wants to review and send by hand.
|
|
71
75
|
- **Ingestion — fetch:** `email-fetch` — list new IMAP messages since the stored high-water mark. Metadata + body preview only — including attachment names, mimetypes, and sizes so the operator sees what's attached before approving (Task 402). No bytes downloaded, no graph writes. Stages the batch in-memory for `email-ingest`.
|
|
72
76
|
- **Ingestion — apply:** `email-ingest` — apply the operator's per-message decisions to the staged batch. Each Message-ID must carry a disposition (`ingest` or `discard`). Approved messages land on `:ConversationArchive {source:'email'}` via the source-agnostic conversation-archive pipeline (one archive per thread, sessionised into `:Section` chunks). Attachment bytes are fetched, archived to `{accountDir}/archive/email/<uidValidity>-<uid>/<sha256>-<filename>` (0o600, 25 MB cap), and each attachment lands as a content-addressed `:DigitalDocument` keyed on `attachmentId = sha256(bytes)`; the `:HAS_ENCLOSURE` edge from the parent `:ConversationArchive` is wired by `memory-ingest` on its next pass via the `pendingArchiveEdges` queue (Task 402). Advances the inbox high-water mark only after a successful write.
|
|
73
77
|
- **Recall/history:** `email-graph-query` — search stored email archives in Neo4j by natural language, participant address, or date range. Each thread is one `:ConversationArchive {source:'email'}` with `:Section` chunks; operator participants attach via `:PARTICIPANT_IN` edges from `:Person`/`:AdminUser`. List mode surfaces a per-archive summary built from concatenated `:Section.summary` values; query mode runs vector search over the `section_embedding` index and dedups to the parent archive.
|
|
@@ -89,7 +93,7 @@ Email ingestion is **operator-initiated and human-in-the-loop**. There is no bac
|
|
|
89
93
|
| Skill | Purpose |
|
|
90
94
|
|-------|---------|
|
|
91
95
|
| [email-ingest](skills/email-ingest/SKILL.md) | Operator-initiated ingestion. Drives "check my emails" → list via `email-fetch` → summarise → ask the operator per message → write the approved subset via `email-ingest`. |
|
|
92
|
-
| [email-composition](skills/email-composition/SKILL.md) | Conversational wrapper for drafting replies, new emails, and inbox triage. Orchestrates the
|
|
96
|
+
| [email-composition](skills/email-composition/SKILL.md) | Conversational wrapper for drafting replies, new emails, and inbox triage. Orchestrates the six `email-*` tools with human-in-the-loop approval on every send; draft intent ("leave it in drafts", "don't send yet") routes to `email-draft`. |
|
|
93
97
|
|
|
94
98
|
## References
|
|
95
99
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* MCP spawn-tee — parent-side stderr capture + lifecycle observability.
|
|
5
|
+
*
|
|
6
|
+
* Claude Code spawns each MCP server itself; the platform never holds a
|
|
7
|
+
* ChildProcess handle. This wrapper sits between Claude Code and the real
|
|
8
|
+
* MCP server: Claude Code runs `node <this> <real-entry>`, and the wrapper
|
|
9
|
+
* spawns the real entry with `stdio:['inherit','inherit','pipe']` so stdin
|
|
10
|
+
* and stdout (the JSON-RPC channel) pass through byte-identical. Only stderr
|
|
11
|
+
* is intercepted, and only for logging.
|
|
12
|
+
*
|
|
13
|
+
* Because the wrapper IS the helper's parent, it observes the helper's true
|
|
14
|
+
* exit code, signal, and lifetime for every death mode — including a SIGKILL
|
|
15
|
+
* or a transport-layer crash that an in-process handler would miss.
|
|
16
|
+
*
|
|
17
|
+
* Claude Code CLI → wrapper (this file) → child = node <real-entry>
|
|
18
|
+
* child stdin/stdout : inherited (Claude Code pipe, untouched)
|
|
19
|
+
* child stderr : piped → per-date log + per-session log + passthrough
|
|
20
|
+
*
|
|
21
|
+
* Destinations (Task 706):
|
|
22
|
+
* - `${LOG_DIR}/mcp-<name>-stderr-<date>.log` — per-date raw (back-compat;
|
|
23
|
+
* the in-process mcp-stderr-tee and the [mcp-init-error] probe read it).
|
|
24
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — per-session raw + lifecycle
|
|
25
|
+
* lines; wrapper-only, so it never mixes the enumeration copy with the
|
|
26
|
+
* live copy. Authoritative sink. Absent when SESSION_ID is unset
|
|
27
|
+
* (enumeration spawn) — then the per-date file is the fallback.
|
|
28
|
+
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
29
|
+
* the [mcp-helper] lifecycle lines. A dropped POST loses nothing because
|
|
30
|
+
* the per-session file already holds the line (written synchronously).
|
|
31
|
+
*
|
|
32
|
+
* Lifecycle lines, correlation key `session=<id8> server=<name>`:
|
|
33
|
+
* [mcp-helper] op=spawn ... pid= entry=
|
|
34
|
+
* [mcp-helper] op=boot ... head=<first stderr bytes>
|
|
35
|
+
* [mcp-helper] op=exit ... code= signal= lifetimeMs= stderr-tail= (always)
|
|
36
|
+
*
|
|
37
|
+
* The wrapper never writes to fd 1 (stdout) — that is the JSON-RPC channel.
|
|
38
|
+
* SIGTERM/SIGINT are forwarded to the child so a Claude Code shutdown does
|
|
39
|
+
* not orphan it; the child exit code/signal is propagated verbatim.
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
const node_child_process_1 = require("node:child_process");
|
|
43
|
+
const node_fs_1 = require("node:fs");
|
|
44
|
+
const node_path_1 = require("node:path");
|
|
45
|
+
const SERVER_NAME = process.env.MCP_SPAWN_TEE_NAME ?? "unknown";
|
|
46
|
+
const LOG_DIR = process.env.LOG_DIR;
|
|
47
|
+
const SESSION_ID = process.env.SESSION_ID;
|
|
48
|
+
const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
49
|
+
const ENTRY = process.argv[2];
|
|
50
|
+
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
51
|
+
const spawnStamp = Date.now();
|
|
52
|
+
// Per-session raw + lifecycle log (Task 706). Wrapper-only. Empty SESSION_ID
|
|
53
|
+
// (enumeration spawn) → undefined, and the per-date file is the fallback.
|
|
54
|
+
const perSessionPath = LOG_DIR && SESSION_ID
|
|
55
|
+
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-${SESSION_ID}.log`)
|
|
56
|
+
: undefined;
|
|
57
|
+
const perDatePath = LOG_DIR
|
|
58
|
+
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-stderr-${new Date(spawnStamp).toISOString().slice(0, 10)}.log`)
|
|
59
|
+
: undefined;
|
|
60
|
+
// Rolling tail of child stderr for op=exit. Capped so a chatty child cannot
|
|
61
|
+
// grow the buffer without bound.
|
|
62
|
+
const TAIL_CAP = 2048;
|
|
63
|
+
let stderrTail = "";
|
|
64
|
+
let bootEmitted = false;
|
|
65
|
+
function appendSafe(path, data) {
|
|
66
|
+
if (!path || !LOG_DIR)
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
(0, node_fs_1.mkdirSync)(LOG_DIR, { recursive: true });
|
|
70
|
+
(0, node_fs_1.appendFileSync)(path, data);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* unwritable destination — never mask primary output */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Best-effort mirror of a lifecycle line to server.log via the loopback
|
|
77
|
+
// log-ingest route. Fire-and-forget: the per-session file is authoritative,
|
|
78
|
+
// so a dropped POST (unreachable port, process exiting) loses nothing.
|
|
79
|
+
function postToServerLog(suffix, level) {
|
|
80
|
+
if (!PLATFORM_PORT)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
const ctrl = new AbortController();
|
|
84
|
+
const t = setTimeout(() => ctrl.abort(), 500);
|
|
85
|
+
t.unref?.(); // never let the abort timer keep the wrapper's event loop alive
|
|
86
|
+
void fetch(`http://127.0.0.1:${PLATFORM_PORT}/api/admin/log-ingest`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: { "content-type": "application/json" },
|
|
89
|
+
body: JSON.stringify({ tag: "mcp-helper", level, line: suffix }),
|
|
90
|
+
signal: ctrl.signal,
|
|
91
|
+
}).then(() => clearTimeout(t)).catch(() => clearTimeout(t));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* fetch threw synchronously — best-effort only */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Emit one [mcp-helper] lifecycle line: authoritative per-session file (sync,
|
|
98
|
+
// survives process.exit), the wrapper's own stderr (journald / Claude Code
|
|
99
|
+
// visibility), and a best-effort server.log mirror. `suffix` is the line body
|
|
100
|
+
// after the tag and carries no newline (the log-ingest route rejects newlines).
|
|
101
|
+
function emitLifecycle(suffix, level) {
|
|
102
|
+
const line = `[mcp-helper] ${suffix}\n`;
|
|
103
|
+
appendSafe(perSessionPath, line);
|
|
104
|
+
try {
|
|
105
|
+
process.stderr.write(line);
|
|
106
|
+
}
|
|
107
|
+
catch { /* stderr closed */ }
|
|
108
|
+
postToServerLog(suffix, level);
|
|
109
|
+
}
|
|
110
|
+
if (!ENTRY) {
|
|
111
|
+
emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason="no entry given (argv[2] missing)"`, "error");
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [ENTRY], {
|
|
115
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
116
|
+
env: process.env,
|
|
117
|
+
});
|
|
118
|
+
emitLifecycle(`op=spawn session=${SESSION_ID8} server=${SERVER_NAME} pid=${child.pid ?? -1} entry=${ENTRY}`, "info");
|
|
119
|
+
child.on("error", (err) => {
|
|
120
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
121
|
+
emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason=${JSON.stringify(`spawn error: ${msg}`)}`, "error");
|
|
122
|
+
process.exit(127);
|
|
123
|
+
});
|
|
124
|
+
if (child.stderr) {
|
|
125
|
+
child.stderr.on("data", (chunk) => {
|
|
126
|
+
appendSafe(perDatePath, chunk); // per-date raw (back-compat)
|
|
127
|
+
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
128
|
+
try {
|
|
129
|
+
process.stderr.write(chunk);
|
|
130
|
+
}
|
|
131
|
+
catch { /* stderr closed */ } // passthrough
|
|
132
|
+
stderrTail = (stderrTail + chunk.toString("utf8")).slice(-TAIL_CAP);
|
|
133
|
+
if (!bootEmitted) {
|
|
134
|
+
bootEmitted = true;
|
|
135
|
+
const head = chunk.toString("utf8").split("\n")[0].slice(0, 200);
|
|
136
|
+
emitLifecycle(`op=boot session=${SESSION_ID8} server=${SERVER_NAME} head=${JSON.stringify(head)}`, "info");
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const forward = (signal) => { if (!child.killed)
|
|
141
|
+
child.kill(signal); };
|
|
142
|
+
process.on("SIGTERM", () => forward("SIGTERM"));
|
|
143
|
+
process.on("SIGINT", () => forward("SIGINT"));
|
|
144
|
+
// Signal → number, so a signal-killed child propagates as 128+signum (the
|
|
145
|
+
// shell convention). `code` is null on a signal exit, so `128 + (code ?? 0)`
|
|
146
|
+
// would collapse every signal to 128 and lose the signal identity in the
|
|
147
|
+
// wrapper's own exit status.
|
|
148
|
+
const SIGNUM = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGKILL: 9, SIGTERM: 15 };
|
|
149
|
+
child.on("exit", (code, signal) => {
|
|
150
|
+
const lifetimeMs = Date.now() - spawnStamp;
|
|
151
|
+
const tail = stderrTail.slice(-200);
|
|
152
|
+
const level = signal || (code ?? 0) !== 0 ? "error" : "info";
|
|
153
|
+
emitLifecycle(`op=exit session=${SESSION_ID8} server=${SERVER_NAME} pid=${child.pid ?? -1} ` +
|
|
154
|
+
`code=${code ?? "—"} signal=${signal ?? "—"} lifetimeMs=${lifetimeMs} stderr-tail=${JSON.stringify(tail)}`, level);
|
|
155
|
+
if (signal)
|
|
156
|
+
process.exit(128 + (SIGNUM[signal] ?? 0));
|
|
157
|
+
process.exit(code ?? 0);
|
|
158
|
+
});
|
|
159
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-draft.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/email-draft.test.ts"],"names":[],"mappings":""}
|