@rubytech/create-maxy-code 0.1.497 → 0.1.499
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/active-rules/dist/index.d.ts +3 -7
- package/payload/platform/lib/active-rules/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/active-rules/dist/index.js +20 -52
- package/payload/platform/lib/active-rules/dist/index.js.map +1 -1
- package/payload/platform/lib/active-rules/src/index.test.ts +35 -69
- package/payload/platform/lib/active-rules/src/index.ts +17 -58
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +55 -9
- package/payload/platform/plugins/docs/references/admin-ui.md +36 -6
- package/payload/platform/plugins/docs/references/internals.md +2 -2
- package/payload/platform/plugins/docs/references/sweep-guide.md +16 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.js +53 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.js +50 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/compose.js +15 -2
- package/payload/platform/plugins/email/mcp/dist/lib/compose.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/signature.d.ts +29 -0
- package/payload/platform/plugins/email/mcp/dist/lib/signature.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/signature.js +55 -0
- package/payload/platform/plugins/email/mcp/dist/lib/signature.js.map +1 -0
- package/payload/platform/plugins/email/references/email-reference.md +10 -0
- package/payload/platform/plugins/memory/PLUGIN.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/scripts/__tests__/dream-cycle-sweep-decision.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/__tests__/dream-cycle-sweep-decision.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/__tests__/dream-cycle-sweep-decision.test.js +84 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/__tests__/dream-cycle-sweep-decision.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep-decision.d.ts +54 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep-decision.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep-decision.js +58 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep-decision.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.d.ts +30 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js +204 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +30 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js +10 -9
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +4 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +38 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +10 -4
- package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +2 -0
- package/payload/platform/plugins/memory/skills/preference-audit/SKILL.md +16 -0
- package/payload/platform/plugins/sweep/PLUGIN.md +5 -1
- package/payload/platform/plugins/sweep/skills/email-graph-sweep/SKILL.md +165 -0
- package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts +19 -19
- package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js +28 -34
- package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js.map +1 -1
- package/payload/server/package.json +2 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/assets/{AdminLoginScreens-BXDYzNbq.js → AdminLoginScreens-Ce5aBBWF.js} +1 -1
- package/payload/server/public/assets/AdminShell-BFVVkWFC.js +2 -0
- package/payload/server/public/assets/{activity-cVe5ZnG7.js → activity-CKUYOLd2.js} +1 -1
- package/payload/server/public/assets/admin-BWb8me-E.js +1 -0
- package/payload/server/public/assets/{arc-BrHWN2Kr.js → arc-CpWWSum8.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-D-AX2VuA.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-1OxSL3rT.js → architectureDiagram-Q4EWVU46-D2EzaII0.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-C6yKiblF.js → blockDiagram-DXYQGD6D-8kDpaNSZ.js} +1 -1
- package/payload/server/public/assets/{bot-Bb8yj02k.js → bot-DESMpxSQ.js} +1 -1
- package/payload/server/public/assets/{browser-BdWeCfYJ.js → browser-CpLq_DIo.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-BzTyHS1I.js → c4Diagram-AHTNJAMY-CbwmZpK3.js} +1 -1
- package/payload/server/public/assets/{calendar-B4KbpiXR.js → calendar-ClOwyuz-.js} +1 -1
- package/payload/server/public/assets/channel-DZ5sh6YQ.js +1 -0
- package/payload/server/public/assets/chat-MvnxPJZ5.js +1 -0
- package/payload/server/public/assets/chevron-left-BzIg4in6.js +1 -0
- package/payload/server/public/assets/chevron-right-BcFSa9C3.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-BGAUfWvM.js → chunk-2KRD3SAO-PFheZ9mw.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-M1H_u2Qv.js → chunk-336JU56O-CCFOp5VK.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-CcjBLp4a.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-CkxXXCiF.js → chunk-4BX2VUAB-Hz4JWOf2.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-DPWZYGHH.js → chunk-4TB4RGXK-4xeV0s85.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-CBkNmqrt.js → chunk-55IACEB6-4QKgBpoh.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-uIqIu9ha.js → chunk-5FUZZQ4R-ms3K6Z5z.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-B_S7XzpE.js → chunk-5PVQY5BW-CIC4GIGi.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-BbMV1lgW.js → chunk-67CJDMHE-B-XrWVmV.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-C49Mv-nQ.js → chunk-7N4EOEYR-C7ECOUYQ.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-Dy_pJOp_.js → chunk-AA7GKIK3-DOZ6Fxwk.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-Ce-OmPqn.js → chunk-BSJP7CBP-4cJ_b0c6.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-tnjq-APC.js → chunk-CIAEETIT--mhZipWM.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-uzO7kO4e.js → chunk-EDXVE4YY-jZpVwob_.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-DQMOsG1f.js → chunk-ENJZ2VHE-Nn-p-xIA.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-Top6y08S.js → chunk-FMBD7UC4-DyNpcKOS.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-B9LPNCUS.js → chunk-FOC6F5B3-C0P8q1uz.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-DeIt3WoY.js → chunk-ICPOFSXX-CdXQ5SfI.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-DAnRM04h.js → chunk-K5T4RW27-BVZJKKsT.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-C0QYcbLe.js → chunk-KGLVRYIC-QlVWTe0V.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-BjvA5ykt.js → chunk-LIHQZDEY-CpGVZutP.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-Cs46Gj7z.js → chunk-ORNJ4GCN-WVcS6fUC.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-CBHIFMBx.js → chunk-OYMX7WX6-B7hiSTed.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-B2WjDYBE.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-5_DL8yx6.js → chunk-U2HBQHQK-DJPnZxGy.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-BDCaWRe9.js → chunk-X2U36JSP-DilBs1Yc.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-B10QuxYb.js → chunk-XPW4576I-B5-uyrJ0.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-81jJqyDd.js → chunk-YZCP3GAM-BNO4z0Mv.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-DGx3J6Dx.js → chunk-ZZ45TVLE-BzHMQpip.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CT3jNrze.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DJRsjAkC.js +1 -0
- package/payload/server/public/assets/clock-Dr3NGtjR.js +1 -0
- package/payload/server/public/assets/clone-FcEDnoMT.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-CRDvLRXs.js → cose-bilkent-S5V4N54A-3DWYmn1o.js} +1 -1
- package/payload/server/public/assets/{dagre--rdjN6xZ.js → dagre-Cpy31xJW.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-DHbVQPnV.js → dagre-KV5264BT-C8fyxHFd.js} +1 -1
- package/payload/server/public/assets/data-BjqC4hyM.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-D8HC1Idg.js → diagram-5BDNPKRD-ycyhnLvr.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-evvg8aey.js → diagram-G4DWMVQ6-BkeYicIt.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-4hZuk-Dd.js → diagram-MMDJMWI5-BcGZU-Q3.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-Cwei1ql9.js → diagram-TYMM5635-DuBDCFb_.js} +1 -1
- package/payload/server/public/assets/{dist-B2N0v8hZ.js → dist-BYc_arNy.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-BG5OK4Fx.js → erDiagram-SMLLAGMA-DmkeOzvp.js} +1 -1
- package/payload/server/public/assets/{flatten-naJ5PJ4f.js → flatten-QjnJ1Gcl.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-CPB_poda.js → flowDiagram-DWJPFMVM-CkE-pjuB.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CD6BL1UP.js → ganttDiagram-T4ZO3ILL-Cjiy3kvE.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-D1uhnP9U.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-C-e8FDGn.js → gitGraphDiagram-UUTBAWPF-CsI4lKZm.js} +1 -1
- package/payload/server/public/assets/{graph-COyGp2x1.js → graph-B7niLM97.js} +3 -3
- package/payload/server/public/assets/{graph-labels-CR-HqNdV.js → graph-labels-DykmXYHA.js} +1 -1
- package/payload/server/public/assets/{graphlib-CdCZY1Mj.js → graphlib-BkXxC6D5.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-oNm27GAV.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-LxTftqyS.js +2 -0
- package/payload/server/public/assets/{isEmpty-Bnpg7b8f.js → isEmpty-BavJI9Cn.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CFLhfPpo.js → ishikawaDiagram-UXIWVN3A-D8lZtjRc.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-BN0PaMgT.js → journeyDiagram-VCZTEJTY-kLE7SiR_.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-CmRilACp.js → kanban-definition-6JOO6SKY-ove6JuD3.js} +1 -1
- package/payload/server/public/assets/{line-DR6Zk7d6.js → line-CZMSoxM6.js} +1 -1
- package/payload/server/public/assets/{linear-CAOWPodQ.js → linear-Be8IK9hK.js} +1 -1
- package/payload/server/public/assets/{maximize-2-ne1kaQlS.js → maximize-2-BbzPm7o8.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-0vmQl6Gn.js → mermaid-parser.core-BBADqbnp.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-DWgpHiLh.js → mermaid.core-mH3N7K0I.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CViR7VGV.js → mindmap-definition-QFDTVHPH-BjwwwWil.js} +1 -1
- package/payload/server/public/assets/operator-DCNWUKP_.js +1 -0
- package/payload/server/public/assets/{ordinal-Wx4m9zHk.js → ordinal-BHeIKG-K.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-eQNVGiK3.js +1 -0
- package/payload/server/public/assets/{page-DwfYACyC.js → page-C4TrTU6r.js} +4 -4
- package/payload/server/public/assets/page-DUgrGrJp.js +1 -0
- package/payload/server/public/assets/{pdf-render-0hyh3Jlf.js → pdf-render-fJz4vym9.js} +1 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-E1u-48rt.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-B7YQ1_Kr.js → pieDiagram-DEJITSTG-D-lXWWta.js} +1 -1
- package/payload/server/public/assets/{public-BACeh5xm.js → public-DXJXQPwe.js} +1 -1
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-Dxs0nGu5.js → quadrantDiagram-34T5L4WZ-13QyG_AO.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-Cn2cGN90.js +1 -0
- package/payload/server/public/assets/{reduce-BUmc8RDR.js → reduce-Fm685to3.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-DDNYDqZc.js → requirementDiagram-MS252O5E-Bx45Bgn4.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-tY4mfSrJ.js → rotate-ccw-L42fL8ey.js} +1 -1
- package/payload/server/public/assets/routines-JOV6xdYh.js +1 -0
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DLWoegU5.js → sankeyDiagram-XADWPNL6-Cj-Gq25v.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DINNPlTY.js → sequenceDiagram-FGHM5R23-B8RDRq93.js} +1 -1
- package/payload/server/public/assets/{src-DHSQDYx-.js → src-D3VYuW_Z.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-Dsan8EIl.js → stateDiagram-FHFEXIEX-DoyFF1Kl.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Bml-X4-H.js +1 -0
- package/payload/server/public/assets/{tasks-C4nPXsbX.js → tasks-GNtvZL8F.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-CmqjpGG2.js → time-entry-format-BreNpFLJ.js} +1 -1
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-CISzecz5.js → timeline-definition-GMOUNBTQ-Df4RQXm6.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CimabhXy.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-Cg264rpM.js +1 -0
- package/payload/server/public/assets/{triangle-alert-DKZ69dOa.js → triangle-alert-DumCWC08.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-Cf5jFDR5.js → useCopyFeedback-DHCtz8qX.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-Bc7XHr0L.js +9 -0
- package/payload/server/public/assets/{useSubAccountSwitcher-DFnDNIDO.css → useSubAccountSwitcher-DPHQJO7E.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-BVdKbTku.js → useVoiceRecorder-Cu6zvuFx.js} +2 -2
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Bh2s76nJ.js → vennDiagram-DHZGUBPP-D2o5og9l.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-tbgfCg0E.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-Grzw0FHg.js → wardleyDiagram-NUSXRM2D-BDueObbS.js} +1 -1
- package/payload/server/public/assets/wrench-iWkROE1V.js +1 -0
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-7pY90HjH.js → xychartDiagram-5P7HB3ND-BTYs4Fgh.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +8 -7
- package/payload/server/public/chat.html +15 -14
- package/payload/server/public/data.html +12 -12
- package/payload/server/public/graph.html +10 -10
- package/payload/server/public/index.html +16 -16
- package/payload/server/public/operator.html +16 -15
- package/payload/server/public/public.html +15 -14
- package/payload/server/public/routines.html +20 -0
- package/payload/server/public/tasks.html +7 -7
- package/payload/server/server.js +464 -260
- package/payload/server/public/assets/AdminShell-CiTHCp5t.js +0 -2
- package/payload/server/public/assets/admin-BbcLizN0.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-CZQzGRSM.js +0 -1
- package/payload/server/public/assets/channel-uhtvXKHn.js +0 -1
- package/payload/server/public/assets/chat-BEcbZB3e.js +0 -1
- package/payload/server/public/assets/chevron-left-Cy_RXwhn.js +0 -1
- package/payload/server/public/assets/chevron-right-CEjdB4AT.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-D4XtWgLR.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-Qlc2PFAj.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CBH5ngSm.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Ck3eNZce.js +0 -1
- package/payload/server/public/assets/clone-Cv8evfNb.js +0 -1
- package/payload/server/public/assets/data-L-hMHLBN.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CBZPQJLj.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-1vIoX-kP.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-BDInZV9G.js +0 -2
- package/payload/server/public/assets/operator-Q0F_EEyq.js +0 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-CTU0gCGb.js +0 -1
- package/payload/server/public/assets/page-bRjlk8Dt.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-D-EEHxhC.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-DHXATZGB.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-B4yeYYgL.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CC7ouMFd.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-CaCg27im.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-DdctgpPL.js +0 -9
- package/payload/server/public/assets/wardley-RL74JXVD-bMPsqQZ7.js +0 -1
- /package/payload/server/public/assets/{_baseFor-k8hYPe-U.js → _baseFor-Bvwvhrz4.js} +0 -0
- /package/payload/server/public/assets/{array-BFDiaBgf.js → array-D3d-WgFW.js} +0 -0
- /package/payload/server/public/assets/{chunk-TPjIAO9U.js → chunk-fQC6bEKA.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-Ch0xptA4.js → cytoscape.esm-Cun-OM6a.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-ZeknFqNe.js → defaultLocale-CisKV5Ew.js} +0 -0
- /package/payload/server/public/assets/{init-0VJVrkRJ.js → init-D9ZOVaZ3.js} +0 -0
- /package/payload/server/public/assets/{katex-Br7bH10N.js → katex-C-3ZDK8o.js} +0 -0
- /package/payload/server/public/assets/{path-INs8XTPH.js → path-BMOgQSLM.js} +0 -0
- /package/payload/server/public/assets/{preload-helper-CH6UZRzu.js → preload-helper-DL2DwvxV.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-DU9bZLvZ.js → rough.esm-BrTFrov0.js} +0 -0
|
@@ -89,6 +89,35 @@ either is a regression.
|
|
|
89
89
|
|
|
90
90
|
**Public visitor surface.** The public-host root (`GET /`), the `/:slug` agent routes, and the admin-host `/public` / `/public-chat` previews all serve one shell, `public.html` → [`app/public-entry.tsx`](../../../ui/app/public-entry.tsx) → `PublicChat`. `PublicChat` reuses `useSession` purely as the magic-link gatekeeper — it drives `AccessGate`, resolves the agent slug from the path, and handles `?token=` verification; once the grant is satisfied it mounts `ChatSurface variant="public"`, whose transcript reads the visitor-scoped, **delivered-only** stream `GET /api/public-reader/stream` ([`server/routes/public-reader.ts`](../../../ui/server/routes/public-reader.ts)) — the visitor sees the agent's delivered prose, never the tool/tool-result/directive bytes the legacy `/api/chat` SSE render exposed. The human visitor's branding is resolved client-side by `useSession`. For the head only, the server injects per-agent link-preview meta — `<title>`, `og:title`/`og:description`/`og:image`, `theme-color`, and a per-agent favicon, resolved from the same branding cache `useSession` reads — into the served shell, so a link-preview crawler (which never runs the client bundle) gets a branded card per agent instead of one generic shell; an agent with no branding cache gets the clean brand-default shell with no empty meta tags. There is exactly one public client surface and it is 1:1 visitor↔agent: the earlier `?surface=next` A/B handle is retired, group messaging is retired (not a supported product surface), and a former `/g/<slug>` group URL now serves the same 1:1 shell — a stale bookmark gets the 1:1 chat, never a broken render.
|
|
91
91
|
|
|
92
|
+
### Routines
|
|
93
|
+
|
|
94
|
+
The Routines surface (`/routines`) is the admin view of the account's
|
|
95
|
+
automation `:Event` nodes — the device's scheduled agent turns and tool
|
|
96
|
+
dispatches, which the Calendar surface deliberately excludes (`calendar.ts`
|
|
97
|
+
unions only appointment events, `actionPlugin IS NULL`). It renders one card per
|
|
98
|
+
routine (title, human-readable schedule, next run, channel/destination or tool,
|
|
99
|
+
status); a card opens a detail modal modeled on the calendar `EventDetailModal`.
|
|
100
|
+
The modal edits the prose instruction (`agentPrompt`, agent-dispatch routines
|
|
101
|
+
only) and the schedule via a friendly builder (frequency daily/weekly/monthly,
|
|
102
|
+
day-of-week, day-of-month, time) that falls back to a raw-cron field for patterns
|
|
103
|
+
it cannot represent.
|
|
104
|
+
|
|
105
|
+
The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.ts`
|
|
106
|
+
(a foreign-account or non-automation `eventId` 404s):
|
|
107
|
+
|
|
108
|
+
| Route | Behaviour |
|
|
109
|
+
|-------|-----------|
|
|
110
|
+
| `GET /api/admin/routines` | Lists automation Events: `recurrence IS NOT NULL OR agentChannel IS NOT NULL OR actionPlugin IS NOT NULL`. Keyed by the `eventId` property. |
|
|
111
|
+
| `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
|
|
112
|
+
| `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt` and/or `recurrence`. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
|
|
113
|
+
|
|
114
|
+
Observability rides `[admin:routines]`: `op=list accountId=<8> count=<n>`,
|
|
115
|
+
`op=get id=<8> found=<bool>`, and `op=patch id=<8> fields=<…> outcome=ok|rejected
|
|
116
|
+
reason=<invalid-cron|not-found> nextRun=<…> cron=<serialized>`. A `count=0` while
|
|
117
|
+
routines exist for the account is the account-scope-miss signature; the logged
|
|
118
|
+
`cron=` on `outcome=ok` confirms the persisted expression without opening the
|
|
119
|
+
graph.
|
|
120
|
+
|
|
92
121
|
### Graph
|
|
93
122
|
|
|
94
123
|
| Mount | Purpose |
|
|
@@ -397,12 +426,13 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
397
426
|
> owns all navigation; the full-width top header bar, the burger flyout, and the
|
|
398
427
|
> "Powered by Claude Code" footer strip are retired. After the redesign:
|
|
399
428
|
>
|
|
400
|
-
> - **Nav rows** are Dashboard, Chat, Tasks, Calendar,
|
|
401
|
-
>
|
|
402
|
-
>
|
|
403
|
-
>
|
|
404
|
-
>
|
|
405
|
-
>
|
|
429
|
+
> - **Nav rows** are Dashboard, Chat, Tasks, Calendar, Routines (the automation
|
|
430
|
+
> `:Event` nodes at `/routines`, `Zap` icon), Data, the brand container
|
|
431
|
+
> row (Projects / Properties / Jobs, `Folder` icon, opening the graph filtered
|
|
432
|
+
> to the brand's primary container), Graph (the unfiltered whole graph at
|
|
433
|
+
> `/graph`, waypoints icon — active on plain `/graph`; the container row is
|
|
434
|
+
> active only when a `label=` filter is present), Activity, and Browser. The
|
|
435
|
+
> active row carries an accent left indicator. The older People / Agents / Artefacts entries in the
|
|
406
436
|
> table below are pre-redesign.
|
|
407
437
|
> - **Dashboard home** renders on `/` (a greeting, stat cards, a Live-now column,
|
|
408
438
|
> a Recent artefacts card, and a Channels column), consuming existing endpoints
|
|
@@ -400,13 +400,13 @@ Standing rules the operator gives over a channel are `Preference` nodes. Two mec
|
|
|
400
400
|
|
|
401
401
|
**Reconcile by meaning on write (`profile-update`).** The existence check is two-tier. First an exact `(category, key)` match — the classic fast path, unchanged. When that misses, a semantic-neighbour lookup runs over the `preference_embedding` vector index, scoped to the same `accountId` and `scope:'admin'`, non-trashed. If the nearest neighbour is at or above `SAME_SUBJECT_COSINE` (0.85), the write lands on that node through the existing reinforce/update/contradict curve instead of minting a duplicate; the matched neighbours are returned in the tool result so the agent can classify future writes against real prior state. The confidence constants are unchanged — only the join key widens, so a rephrasing under a new `key` no longer forks the rule and a correction demotes the prior node rather than adding a contradictory one.
|
|
402
402
|
|
|
403
|
-
**Inject the active rule-set every turn (channel gateways).** The one definition of "this account's active rules" is `@maxy/active-rules`: the account owner's admin-scope preferences (resolve the seeded `AdminUser {role:'owner'}`, then select through that owner's `UserProfile` via `HAS_PREFERENCE`), a
|
|
403
|
+
**Inject the active rule-set every turn (channel gateways).** The one definition of "this account's active rules" is `@maxy/active-rules`: the account owner's admin-scope preferences (resolve the seeded `AdminUser {role:'owner'}`, then select through that owner's `UserProfile` via `HAS_PREFERENCE`), a floor-filtered (`confidence >= 0.4`), confidence-ordered query that returns every such preference verbatim — no cap, no de-confliction, no candidate limit. Concision is enforced at write time by the `conversational-memory` skill (each stored value is a single minimal imperative) and by the write-time semantic reconcile (a same-subject write at or above `SAME_SUBJECT_COSINE` folds into the existing node rather than minting a duplicate), so the injected block stays small because each stored preference is short and non-redundant, not because injection truncates. A preference owned by a non-owner profile (an operator's, stranded on the operator's own profile while managing the account) is never injected; an account with no seeded owner injects nothing. `profile-read` exposes this same owner-scoped set as `activeRules`, and the three inbound gateways (`wa-gateway`, `telegram-gateway`, `webchat-gateway`) fetch it for the effective session account and attach it as `standingRules` on the inbound frame; the channel builders prepend it as a `## Standing rules` block above `## Context`. Both channel-server inbound paths carry the block: the single-target SSE loop parses the whole frame, and the unified multi-target door reader (`runDoorStream`,) reconstructs it through the shared `doorInboundPayload` seam whose `parseFrame` closures read `standingRules` off the frame. The read is by code, not agent discretion, so a rule set last week is applied this week regardless of how many times the session compacted. Telegram and webchat inject for `role:'admin'` only — a public visitor never receives operator rules.
|
|
404
404
|
|
|
405
405
|
**Trust a scheduled turn's provenance, never an inbound's claim (channel-agent grounding).** A cron-dispatched agent turn reaches the same channel builders as a real inbound, so its own authorisation text would otherwise land inside `## Context` — the zone `REFRAME_INSTRUCTION` demotes to untrusted — and a security-cautious agent would distrust its own automation (the self-cancel loop). The dispatcher therefore stamps a `ScheduleProvenance` marker (owning account, event, owner userId, dispatch time) that the channel builders render as a `## Scheduled automation (platform-verified)` preamble **above** `## Context` (and above the rules block); the whatsapp wrap tag names the door `scheduler`. Both channel-server paths carry the marker — the single-target loop and the unified multi-target door reader (via the same `doorInboundPayload` seam that carries `standingRules`); the scheduler only dispatches to the whatsapp/telegram door, so a webchat frame never bears one. Because the marker is dispatcher-stamped and never derived from payload text, an inbound message cannot forge it — placement above `## Context` alone is not the trust signal, the dispatcher provenance is. The grounding binds to it in two places: one recipient-neutral clause added byte-identical to the three reframe instructions (a marker-bearing turn is authorised standing automation; a reconfiguration or re-enable is not intrusion), and the admin `IDENTITY.md` "Untrusted input" rule that the agent never states a platform-level authenticity or impersonation verdict it was not given (an unsourced "this is not from you" is its own inference, flagged, never asserted as the platform's). A sender has exactly one session: every inbound, real or scheduled, resolves to the deterministic `adminSessionIdFor(effectiveAccount, destination)`. An earlier change gave each firing its own salted seat so distrust could not accrete across firings, but the inbound hub keys on `senderId` and replaces its subscriber outright, so a seat's channel server attached under the sender's own key and swallowed the sender's REAL messages until it detached — splitting one conversation across two sessions that each denied what the other held. The seat is removed; the authoritative `agentPrompt`, this provenance marker, and the injected standing rules ground each firing without one. Self-cancel attribution is disarmed while that model beds in: the guard used to compare the cancelling turn's session id to a dispatcher stamp, which discriminated only because each firing ran its own seat, so with one session per sender it would report an autonomous loop on every operator cancel. It now always reports false — under-counting a real loop rather than inventing one — until the discriminator is rebuilt from the turn's own dispatched eventId. See `.docs/scheduling-agent-dispatch.md § Operator-provenance marker`, `§ Session model — one session per sender`, and `§ Self-cancel attribution — disarmed`.
|
|
406
406
|
|
|
407
407
|
**Carry what a reply points at (WhatsApp reply quote).** A WhatsApp sender replying to an earlier message produces a quote that `app/lib/whatsapp/inbound/extract.ts` resolves to `{ id, text?, sender? }` and `manager.ts` assigns to the route payload's `replyContext`. That field had no reader: the `server/index.ts` gateway argument omitted it, `AdminInbound` and the hub frame had no such field, and the channel service's `composePayload` emitted the caption and media notes only, so a message saying "prepare an email confirmation for this booking" reached the agent with nothing to resolve "this booking" against. It now rides both WhatsApp admin doors. On the persistent door it travels `server/index.ts` -> `AdminInbound` -> the hub frame the SSE route stringifies -> the channel service's `InboundPayload`, where `composeReplyNote` renders it as a bracketed note at the top of the payload, inside `## Context` and above the caption and any woven media notes: the quoted body is sender-controlled and belongs in the reframed untrusted zone, never in the platform preamble. Both channel-server inbound paths carry it, through the same `doorInboundPayload` seam that carries `standingRules` and the schedule provenance marker: the single-target SSE loop parses the whole frame, and the unified multi-target door reader rebuilds the payload field by field, so a field that seam omits is dropped on multi-target sessions alone. That seam is guarded by a test which reads the `InboundPayload` declaration and the rebuild function rather than a fixture, because the pre-existing byte-identity guard compared one hardcoded payload and so stayed green for every field added after it was written. On the passive account-manager door there is no channel server and no `## Context` frame, so the one-shot's whole initial prompt is read at face value; `composePassivePrompt` therefore puts the BARE sentence above the requirement and never the quoted body. The body arrives from the sender's own client and is never checked against the message store, so quoting it in a platform-voiced bracket on the door with no untrusted zone would let a modified client put words the agent never said into a `work-create` filing. The empty-text drop gate still tests the requirement alone, so a quote never rescues an inbound carrying no requirement of its own. The quoted body is capped at `QUOTED_TEXT_CAP` (500 characters, `…` appended when cut) because it is unbounded sender-controlled input, and the cut lands on whole characters so a body whose 500th boundary falls inside a surrogate pair cannot emit a lone surrogate at a position the sender chose. Only the channel service renders a body, so the cap lives there alone; platform/ui's `app/lib/whatsapp/reply-quote.ts` holds just the bare sentence for the passive door, and both packages hardcode that sentence in their own tests so the two doors cannot drift apart on the wording (the packages never cross-import, the same arrangement `REFRAME_INSTRUCTION` and `MediaRef` use). Telegram and webchat extract no quote at all, so neither carries one. `payloadBytes` measures the whole inner payload, so a reply carrying a quote produces a visibly larger `[reframe] op=applied … payloadBytes=<n>` than the same text without one, and a bare-text-length `payloadBytes` on an inbound whose store row has a non-null `quotedId` is the signature of this defect returning. Because the field is optional at every hop, dropping it from either `server/index.ts` argument would be silent (tsc stays green, and no unit test executes that wiring file), so the `check-reply-context-threaded` build gate asserts both argument sites.
|
|
408
408
|
|
|
409
|
-
**Observability.** `[preference-write] op=reconcile … mode=<reinforce|update|contradict|new> similarity=…` — a stream of `mode=new` for subjects that already exist is the duplicate-minting signature. `[preference-inject] op=inject channel=<wa|tg|web> injected=<N> owner=<8> source=<owner|no-owner|owner-error|fetch-error>` — injection selects only the account owner's admin preferences, so `source=owner injected=0` is a genuinely empty owner, `source=no-owner` is an account with no seeded owner (injects nothing, a missed owner seeding), and `injected=0 source=owner` on an account whose owner holds rules is the write-only regression re-appearing. `[preference-
|
|
409
|
+
**Observability.** `[preference-write] op=reconcile … mode=<reinforce|update|contradict|new> similarity=…` — a stream of `mode=new` for subjects that already exist is the duplicate-minting signature. `[preference-inject] op=inject channel=<wa|tg|web> injected=<N> owner=<8> source=<owner|no-owner|owner-error|fetch-error>` — injection selects only the account owner's admin preferences, so `source=owner injected=0` is a genuinely empty owner, `source=no-owner` is an account with no seeded owner (injects nothing, a missed owner seeding), and `injected=0 source=owner` on an account whose owner holds rules is the write-only regression re-appearing. `[preference-inject] op=reconcile accountId=… stored=… injected=…` — the standing reconciliation audit (claude-session-manager, 5-minute tick) flags any account whose stored active count diverges from what injection surfaces; injection now surfaces every stored active preference verbatim, so `stored == injected` is the healthy signal and any gap is a dropped-row regression (a reintroduced cap, filter, or truncation). A clean pass logs `op=reconcile drifted=0`, the no-event detector.
|
|
410
410
|
|
|
411
411
|
**Consult-before-send gate.** Injection (above) puts the owner's active rules in front of every channel turn, but the growing layer-2 `Preference` set is read on demand with `profile-read`, a per-turn judgement the agent can skip, the miss that dropped a customer signature on a delivered document. `hooks/preference-consult-gate.sh` (admin plugin) is the deliverable-boundary enforcement: a PreToolUse gate on customer-facing document deliverables (`browser-pdf-save`/`SendUserFile` to `memory/users/<phone>/documents/`, and email/Outlook sends carrying attachments) that blocks (exit 2) when no `profile-read` ran after the last user message in the transcript. It fires on documents only, never on casual replies. **Observability:** `[preference-gate] op=allow tool=<name> consulted=true` on an allowed document deliverable, `op=bypass tool=<name> detail=no profile-read this turn` on a block; a stream of `op=bypass` is the un-consulted-send signature. The advisory companion `hooks/preference-consult-directive.sh` injects the two-layer architecture reminder every turn (`[pref-wrapper] op=inject`). The placement half, promoting finished deliverables out of `output/` scratch, is counted by the `data-manager` reconcile audit (`scratch-refs`, `stranded`); making that audit run on a standing periodic cadence is a filed follow-up.
|
|
412
412
|
|
|
@@ -41,3 +41,19 @@ A site capture becomes one of these too: an enquiry becomes a new contact routed
|
|
|
41
41
|
## Running It Again
|
|
42
42
|
|
|
43
43
|
Run a sweep a second time and it proposes nothing it already recorded on the first run, because those records are now in memory and it recognises them. A repeat sweep is quiet on purpose; that is the sign your graph is already up to date.
|
|
44
|
+
|
|
45
|
+
## Automatic email→graph sweep (opt-in)
|
|
46
|
+
|
|
47
|
+
The sweep above runs only when you ask, and it proposes rather than writes. There is a second, opt-in routine that runs on a schedule and writes on its own: the automatic email→graph sweep, set up through the `email-graph-sweep` skill. On each run it reads the new inbound mail on your mailbox, pulls out the organisations, people, and live jobs it finds, checks each against what {{productName}} already holds, saves the genuinely new ones, and then sends you a short report of what it read and saved.
|
|
48
|
+
|
|
49
|
+
Turning it on needs two things, and {{productName}} tells you which is missing if either is absent:
|
|
50
|
+
|
|
51
|
+
- **A connected mailbox.** The routine reads your mail, so a mail account (a standard IMAP mailbox, or a Microsoft 365 mailbox) must be connected. With no mailbox connected there is nothing to sweep, and {{productName}} says so instead of switching anything on.
|
|
52
|
+
- **A place to send the report.** The routine reports over a persistent channel, so you name the WhatsApp number or Telegram chat that receives it, and that destination must already be registered as an admin or account-manager. If it is not registered, {{productName}} tells you that rather than falling back to a silent run.
|
|
53
|
+
|
|
54
|
+
Two things to know before you switch it on:
|
|
55
|
+
|
|
56
|
+
- **It messages you on every run.** It is a report you receive each time, not a silent background job. If you want a quiet, always-on tidy-up of the graph instead, that is a different routine.
|
|
57
|
+
- **It is one-account, deliberate opt-in.** {{productName}} does not turn this on for you automatically, and it is held back from broad rollout for now while a safety fix for scheduled runs lands. Enable it for an account you are watching, not as a default.
|
|
58
|
+
|
|
59
|
+
Turn it off with the same skill: ask to stop the automatic email sweep and {{productName}} cancels the schedule, so no further runs fire.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-signature.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/compose-signature.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } 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 { composeDraft } from '../lib/compose.js';
|
|
6
|
+
const config = { email: 'agent@gls.test', agentAddress: 'agent@gls.test' };
|
|
7
|
+
describe('Task 1952 — composeDraft applies signature', () => {
|
|
8
|
+
let root = '';
|
|
9
|
+
let accountDir = '';
|
|
10
|
+
const accountId = 'gls12345-aaaa';
|
|
11
|
+
const saved = { ...process.env };
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
root = realpathSync(mkdtempSync(join(tmpdir(), 'cmp-')));
|
|
14
|
+
const platformRoot = join(root, 'platform');
|
|
15
|
+
mkdirSync(platformRoot, { recursive: true });
|
|
16
|
+
accountDir = join(root, 'data', 'accounts', accountId);
|
|
17
|
+
mkdirSync(accountDir, { recursive: true });
|
|
18
|
+
process.env.PLATFORM_ROOT = platformRoot;
|
|
19
|
+
delete process.env.ACCOUNT_DIR;
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
process.env = { ...saved };
|
|
23
|
+
if (root)
|
|
24
|
+
rmSync(root, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
it('appends the text signature to the composed body', async () => {
|
|
27
|
+
writeFileSync(join(accountDir, 'email-signature.txt'), 'Regards,\nDale');
|
|
28
|
+
const { raw } = await composeDraft(config, 'pw', accountId, {
|
|
29
|
+
to: 'x@y.test', subject: 'Hi', body: 'Quote attached.',
|
|
30
|
+
});
|
|
31
|
+
const text = raw.toString('utf8');
|
|
32
|
+
expect(text).toContain('Quote attached.');
|
|
33
|
+
expect(text).toMatch(/Regards,\r?\nDale/);
|
|
34
|
+
});
|
|
35
|
+
it('logs applied=true with the account id prefix and asset path', async () => {
|
|
36
|
+
writeFileSync(join(accountDir, 'email-signature.txt'), 'Regards,\nDale');
|
|
37
|
+
const err = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
38
|
+
await composeDraft(config, 'pw', accountId, { to: 'x@y.test', subject: 'Hi', body: 'Body.' });
|
|
39
|
+
const line = err.mock.calls.map((c) => String(c[0])).find((s) => s.includes('op=signature'));
|
|
40
|
+
expect(line).toContain('accountId=gls12345');
|
|
41
|
+
expect(line).toContain('applied=true');
|
|
42
|
+
expect(line).toContain(join(accountDir, 'email-signature'));
|
|
43
|
+
err.mockRestore();
|
|
44
|
+
});
|
|
45
|
+
it('logs applied=false when no signature file is present', async () => {
|
|
46
|
+
const err = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
47
|
+
await composeDraft(config, 'pw', accountId, { to: 'x@y.test', subject: 'Hi', body: 'Body.' });
|
|
48
|
+
const line = err.mock.calls.map((c) => String(c[0])).find((s) => s.includes('op=signature'));
|
|
49
|
+
expect(line).toContain('applied=false');
|
|
50
|
+
err.mockRestore();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=compose-signature.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-signature.test.js","sourceRoot":"","sources":["../../src/__tests__/compose-signature.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/signature.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync, writeFileSync, rmSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { loadSignature, applySignature } from '../lib/signature.js';
|
|
6
|
+
describe('Task 1952 — email signature load + apply', () => {
|
|
7
|
+
let dir = '';
|
|
8
|
+
beforeEach(() => { dir = realpathSync(mkdtempSync(join(tmpdir(), 'sig-'))); });
|
|
9
|
+
afterEach(() => { if (dir)
|
|
10
|
+
rmSync(dir, { recursive: true, force: true }); });
|
|
11
|
+
it('loadSignature returns null fields when no files exist', () => {
|
|
12
|
+
expect(loadSignature(dir)).toEqual({ text: null, html: null, font: null });
|
|
13
|
+
});
|
|
14
|
+
it('loadSignature returns null fields when accountDir is undefined', () => {
|
|
15
|
+
expect(loadSignature(undefined)).toEqual({ text: null, html: null, font: null });
|
|
16
|
+
});
|
|
17
|
+
it('loadSignature reads text and html verbatim and trims font', () => {
|
|
18
|
+
writeFileSync(join(dir, 'email-signature.txt'), 'Regards,\nDale');
|
|
19
|
+
writeFileSync(join(dir, 'email-signature.html'), '<p>Regards,<br>Dale</p>');
|
|
20
|
+
writeFileSync(join(dir, 'email-signature.font'), 'Georgia, serif\n');
|
|
21
|
+
expect(loadSignature(dir)).toEqual({
|
|
22
|
+
text: 'Regards,\nDale',
|
|
23
|
+
html: '<p>Regards,<br>Dale</p>',
|
|
24
|
+
font: 'Georgia, serif',
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
it('applySignature appends text to body byte-for-byte', () => {
|
|
28
|
+
const out = applySignature({ body: 'Hello.' }, { text: 'Regards,\nDale', html: null, font: null });
|
|
29
|
+
expect(out.body).toBe('Hello.\n\nRegards,\nDale');
|
|
30
|
+
expect(out.html).toBeUndefined();
|
|
31
|
+
expect(out.applied).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it('applySignature appends html sig then wraps in font when html present', () => {
|
|
34
|
+
const out = applySignature({ body: 'Hello.', html: '<p>Hello.</p>' }, { text: null, html: '<p>Regards</p>', font: 'Georgia, serif' });
|
|
35
|
+
expect(out.html).toBe('<div style="font-family: Georgia, serif"><p>Hello.</p><p>Regards</p></div>');
|
|
36
|
+
expect(out.applied).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
it('applySignature leaves html unset when caller gave no html', () => {
|
|
39
|
+
const out = applySignature({ body: 'Hi' }, { text: null, html: '<p>Sig</p>', font: 'Arial' });
|
|
40
|
+
expect(out.html).toBeUndefined();
|
|
41
|
+
expect(out.applied).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
it('applySignature applied=false when no signature files present', () => {
|
|
44
|
+
const out = applySignature({ body: 'Hi', html: '<p>Hi</p>' }, { text: null, html: null, font: null });
|
|
45
|
+
expect(out.body).toBe('Hi');
|
|
46
|
+
expect(out.html).toBe('<p>Hi</p>');
|
|
47
|
+
expect(out.applied).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=signature.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.test.js","sourceRoot":"","sources":["../../src/__tests__/signature.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/lib/compose.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/lib/compose.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAkC,MAAM,WAAW,CAAC;AAK7E,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CASxF;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,aAAa,CAAC,CA8DxB"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import MailComposer from "nodemailer/lib/mail-composer/index.js";
|
|
3
4
|
import { buildMailOptions, replySubject } from "./smtp.js";
|
|
4
5
|
import { fetchEnvelopeByMessageId } from "./imap.js";
|
|
5
6
|
import { resolveOutboundAttachments, accountDirFor } from "./attachment-resolve.js";
|
|
7
|
+
import { loadSignature, applySignature } from "./signature.js";
|
|
6
8
|
/**
|
|
7
9
|
* Compile the shared mail options into raw RFC822 bytes via nodemailer's own composer.
|
|
8
10
|
*
|
|
@@ -64,11 +66,22 @@ export async function composeDraft(config, password, accountId, params) {
|
|
|
64
66
|
// value is the source of truth an edit re-resolves the draft by.
|
|
65
67
|
const host = (config.agentAddress || config.email).split("@")[1] || "localhost";
|
|
66
68
|
const draftMessageId = `<${randomUUID()}@${host}>`;
|
|
69
|
+
// Apply the account's saved signature deterministically, before the MIME is
|
|
70
|
+
// built, so every draft carries it without the model adding signature text.
|
|
71
|
+
// Resolve the account dir the same way the attachment path does: the param
|
|
72
|
+
// accountId (the sending account) is authoritative, ACCOUNT_DIR is the
|
|
73
|
+
// fallback, so the signature reads from the same account root the attachments
|
|
74
|
+
// are validated against.
|
|
75
|
+
const sigDir = accountDirFor(accountId) || process.env.ACCOUNT_DIR;
|
|
76
|
+
const sig = loadSignature(sigDir);
|
|
77
|
+
const signed = applySignature({ body: params.body, html: params.html }, sig);
|
|
78
|
+
const assetNote = sigDir ? join(sigDir, "email-signature") : "none";
|
|
79
|
+
console.error(`[email-draft] op=signature accountId=${accountId.slice(0, 8)} applied=${signed.applied} asset=${assetNote}`);
|
|
67
80
|
const sendOptions = {
|
|
68
81
|
to: params.to,
|
|
69
82
|
subject: resolvedSubject,
|
|
70
|
-
body:
|
|
71
|
-
html:
|
|
83
|
+
body: signed.body,
|
|
84
|
+
html: signed.html,
|
|
72
85
|
cc: params.cc,
|
|
73
86
|
bcc: params.bcc,
|
|
74
87
|
attachments,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/lib/compose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,YAAY,MAAM,uCAAuC,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAoB,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/lib/compose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,YAAY,MAAM,uCAAuC,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAoB,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA4B/D;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,OAA4C;IACrE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;QACjD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE;YAC1B,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAChB,OAAO,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAmB,EACnB,QAAgB,EAChB,SAAiB,EACjB,MAA0B;IAE1B,6EAA6E;IAC7E,+DAA+D;IAC/D,MAAM,WAAW,GAAG,0BAA0B,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAE7F,IAAI,eAAuB,CAAC;IAC5B,IAAI,SAA6B,CAAC;IAClC,IAAI,UAA8B,CAAC;IACnC,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAE3C,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,MAAM,wBAAwB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/E,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,kCAAkC,MAAM,CAAC,SAAS,sGAAsG,CACzJ,CAAC;QACJ,CAAC;QACD,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;QAC1B,UAAU,GAAG,GAAG,CAAC,SAAS,CAAC;QAC3B,gBAAgB,GAAG,GAAG,CAAC,SAAS,CAAC;QACjC,eAAe,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,IAAI,cAAc,CAAC,CAAC;IAChE,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QACD,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC;IACnC,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,iEAAiE;IACjE,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC;IAChF,MAAM,cAAc,GAAG,IAAI,UAAU,EAAE,IAAI,IAAI,GAAG,CAAC;IAEnD,4EAA4E;IAC5E,4EAA4E;IAC5E,2EAA2E;IAC3E,uEAAuE;IACvE,8EAA8E;IAC9E,yBAAyB;IACzB,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IACnE,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACpE,OAAO,CAAC,KAAK,CACX,wCAAwC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,YAAY,MAAM,CAAC,OAAO,UAAU,SAAS,EAAE,CAC7G,CAAC;IAEF,MAAM,WAAW,GAAgB;QAC/B,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,OAAO,EAAE,eAAe;QACxB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,WAAW;QACX,SAAS;QACT,UAAU;QACV,SAAS,EAAE,cAAc;KAC1B,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,gBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IACpE,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface Signature {
|
|
2
|
+
text: string | null;
|
|
3
|
+
html: string | null;
|
|
4
|
+
font: string | null;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Load an account's saved email signature from the fixed account-dir convention.
|
|
8
|
+
* `.txt` and `.html` are returned verbatim (appended byte-for-byte). `.font` is a
|
|
9
|
+
* single CSS font-family value, trimmed of surrounding whitespace. A missing file
|
|
10
|
+
* is null, never an error: a draft still composes without a signature.
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadSignature(accountDir: string | undefined): Signature;
|
|
13
|
+
/**
|
|
14
|
+
* Append the signature to a draft's body/html deterministically, with no model
|
|
15
|
+
* judgement. The plain-text signature is appended to body whenever present. The
|
|
16
|
+
* html signature and the font wrap apply only when the caller supplied html:
|
|
17
|
+
* font is HTML-only and has no plain-text analogue. `applied` is true when at
|
|
18
|
+
* least one signature part (text or html) was appended; the font wrap alone does
|
|
19
|
+
* not set it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function applySignature(input: {
|
|
22
|
+
body: string;
|
|
23
|
+
html?: string;
|
|
24
|
+
}, sig: Signature): {
|
|
25
|
+
body: string;
|
|
26
|
+
html?: string;
|
|
27
|
+
applied: boolean;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/lib/signature.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAUD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CASvE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACtC,GAAG,EAAE,SAAS,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAkBnD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
function readIf(path) {
|
|
4
|
+
try {
|
|
5
|
+
return readFileSync(path, "utf8");
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Load an account's saved email signature from the fixed account-dir convention.
|
|
13
|
+
* `.txt` and `.html` are returned verbatim (appended byte-for-byte). `.font` is a
|
|
14
|
+
* single CSS font-family value, trimmed of surrounding whitespace. A missing file
|
|
15
|
+
* is null, never an error: a draft still composes without a signature.
|
|
16
|
+
*/
|
|
17
|
+
export function loadSignature(accountDir) {
|
|
18
|
+
if (!accountDir)
|
|
19
|
+
return { text: null, html: null, font: null };
|
|
20
|
+
const base = join(accountDir, "email-signature");
|
|
21
|
+
const font = readIf(`${base}.font`);
|
|
22
|
+
return {
|
|
23
|
+
text: readIf(`${base}.txt`),
|
|
24
|
+
html: readIf(`${base}.html`),
|
|
25
|
+
font: font ? font.trim() : null,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Append the signature to a draft's body/html deterministically, with no model
|
|
30
|
+
* judgement. The plain-text signature is appended to body whenever present. The
|
|
31
|
+
* html signature and the font wrap apply only when the caller supplied html:
|
|
32
|
+
* font is HTML-only and has no plain-text analogue. `applied` is true when at
|
|
33
|
+
* least one signature part (text or html) was appended; the font wrap alone does
|
|
34
|
+
* not set it.
|
|
35
|
+
*/
|
|
36
|
+
export function applySignature(input, sig) {
|
|
37
|
+
let body = input.body;
|
|
38
|
+
let html = input.html;
|
|
39
|
+
let applied = false;
|
|
40
|
+
if (sig.text !== null) {
|
|
41
|
+
body = `${body}\n\n${sig.text}`;
|
|
42
|
+
applied = true;
|
|
43
|
+
}
|
|
44
|
+
if (html !== undefined) {
|
|
45
|
+
if (sig.html !== null) {
|
|
46
|
+
html = `${html}${sig.html}`;
|
|
47
|
+
applied = true;
|
|
48
|
+
}
|
|
49
|
+
if (sig.font !== null) {
|
|
50
|
+
html = `<div style="font-family: ${sig.font}">${html}</div>`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { body, html, applied };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/lib/signature.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,UAA8B;IAC1D,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC;IACpC,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC;QAC5B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;KAChC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAsC,EACtC,GAAc;IAEd,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACtB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACtB,IAAI,GAAG,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;QAChC,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACtB,IAAI,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5B,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACtB,IAAI,GAAG,4BAA4B,GAAG,CAAC,IAAI,KAAK,IAAI,QAAQ,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC"}
|
|
@@ -169,6 +169,16 @@ To get this resilience, pass the `draftMessageId` from the prior confirmation ba
|
|
|
169
169
|
|
|
170
170
|
If neither the UID nor the Message-ID resolves a live draft, the prior draft is genuinely gone. The edit does not fail — it saves the replacement as a fresh draft and says so (`previous draft no longer present; saved a fresh draft`). The atomicity rule still holds: the replacement is always written before any old copy is removed, so a draft is never left with zero copies.
|
|
171
171
|
|
|
172
|
+
### Signature
|
|
173
|
+
|
|
174
|
+
`email-draft` and `email-draft-edit` apply the account's saved signature to every draft deterministically, so the model never adds signature text itself. The signature lives at a fixed path under the account directory:
|
|
175
|
+
|
|
176
|
+
- `email-signature.txt` is the plain-text signature, appended to the message body.
|
|
177
|
+
- `email-signature.html` is the HTML signature block, appended to the message html.
|
|
178
|
+
- `email-signature.font` is an optional single-line CSS font-family; when present the html part is wrapped in one `font-family` div.
|
|
179
|
+
|
|
180
|
+
The plain-text signature is appended whenever it exists. The html signature and the font wrap apply only when the caller supplied html, because font has no plain-text analogue. The caller passes body and html without the signature, and the tool appends it. Each draft compose logs `[email-draft] op=signature accountId=<id8> applied=<bool> asset=<path>`; `applied=false` on a turn that produced a draft is the failure signal, meaning the asset was missing or the account directory did not resolve.
|
|
181
|
+
|
|
172
182
|
### Transient connection drops
|
|
173
183
|
|
|
174
184
|
A single transient transport drop (socket timeout, unexpected close, connection-not-available) during a draft write is retried once on a fresh connection. Authentication failures and other permanent errors are not retried — they surface immediately.
|
|
@@ -215,6 +215,8 @@ Graph hygiene is **agent-directed, case by case** — no autonomous rule engine,
|
|
|
215
215
|
|
|
216
216
|
`memory-dream-run` is the operator-initiated graph-hygiene sweep — seven phases: orphan detection on `:Person`/`:Organization`/`:Concept`/`:Project`; stale compiled-truth scan on `compiledTruthUpdatedAt`; mark edges to `:Trashed` targets with `staleTarget: true`; LLM-bounded citation audit on `:TimelineEvent`; prune compiled-truth revisions to newest 20 per entity; materialise persistent `:BACKLINKS` edges derived from `:MENTIONS`/`:REFERENCES` (making reverse-link queries O(1)); normalise `tags[]` array properties on `:KnowledgeDocument` nodes and merge `:DefinedTerm {category:'obsidian-tag'}` variants. Each run writes a `:Report` (via `memory-report-write`) summarising counts; per-phase log lines `[dream-cycle] phase=<n>` join on the run id. Failure of any phase logs `[dream-cycle:phase-<n>:error]` and continues. Pass `phases: [4]` to run only the citation audit (e.g. after a bulk ingest); `maxEventsPerRun` defaults to 200 for phase 4, with a resume cursor stored as `:Report.lastEventCursor`. `memory-review-queue` is the read-only surface for `:OrphanCandidate` and `:CitationProposal` queues — counts plus a small sample per queue.
|
|
217
217
|
|
|
218
|
+
**Scheduled sweep.** `memory-dream-run` is also driven automatically, per account, by the self-contained `mcp/dist/scripts/dream-cycle-sweep.js` binary. The always-running `platform/ui` server spawns it hourly (the same `registerLoop` pattern as `calendar-reconcile`), because the Pi has no other periodic surface. Per tick it enumerates every account (`account-enumeration`) and, for each, reads the most-recent dream-cycle `:Report`: an account swept within the last 24h is skipped (`op=skip reason=within-window`), an account with an empty graph is skipped (`op=skip reason=no-graph`), an account with no `:AdminUser` to parent a `:Report` is skipped (`op=skip reason=no-report-parent`, the provisioned-but-pre-PIN state), and any other account runs all seven phases via `memoryDreamRun` (`op=run … reportId=…`). The 24h cadence is enforced from that `:Report`, so the hourly tick is idempotent and needs no scheduler entry. The sweep is deterministic end to end — no LLM, no channel, no agent turn, no Claude credential — and is not a schedulable `:Event`, so the operator cannot cancel it. It PID-locks at `<configDir>/dream-cycle-sweep.lock` against overlap; a missing `dist` (dev tree) makes the server loop a silent no-op. Diagnostic: `logs-read --tail server 200 | grep '\[dream-cycle-sweep\]'`. The opt-in, mailbox-driven counterpart that extracts entities from inbound is a scheduled agent turn, not this deterministic path.
|
|
219
|
+
|
|
218
220
|
## Brain-capture diagnostic — `memory-brain-capture-recent`
|
|
219
221
|
|
|
220
222
|
Read-only window into the extractive graph writes produced under this account. Returns recent `:MENTIONS` edges (`:Message-[:MENTIONS]->Entity`) and `:Idea` nodes interleaved by `observedAt`. Default window is 24 hours; pass `windowHours` (max 720) and `limit` (max 200) to scan a longer arc. Use this when the operator asks "what has the brain captured today?", "show me recent mentions of X", or "what ideas did I leave on the table this week?". Writes are produced by `database-operator` per-write briefs admin dispatches — either inline mid-turn, or as part of the on-demand `/insight` graph-completeness sweep (`platform/plugins/admin/skills/insight/SKILL.md`).
|
|
@@ -547,7 +547,7 @@ process.stderr.write("[memory-mcp] registered tool=conversation-archive-list-chu
|
|
|
547
547
|
// MERGE-key (chunkElementId, kind, contentHash) baked into each mergeCypher.
|
|
548
548
|
// ---------------------------------------------------------------------------
|
|
549
549
|
const claimSchema = z.object({
|
|
550
|
-
kind: z.enum([...PROPOSAL_KINDS]).describe("One of: mention, task, preference, observed-relationship."),
|
|
550
|
+
kind: z.enum([...PROPOSAL_KINDS]).describe("One of: mention, task, preference, note, observed-relationship."),
|
|
551
551
|
evidenceSnippet: z
|
|
552
552
|
.string()
|
|
553
553
|
.describe("Near-verbatim quote from the chunk body (≤80 chars enforced server-side)."),
|
|
@@ -566,7 +566,7 @@ const claimSchema = z.object({
|
|
|
566
566
|
.optional()
|
|
567
567
|
.describe("Kind-specific structured fields (taskTitle/taskDueHint for task; preferenceCategory/preferenceKey/preferenceValue for preference; relationshipType for observed-relationship)."),
|
|
568
568
|
});
|
|
569
|
-
lifelineTool(server, "conversation-archive-derive-insights", "Convert one chunk's agent-produced claims into operator-facing claim proposals (mention, task, preference, observed-relationship). The conversation-archive-enrich skill reads chunk bodies via `conversation-archive-list-chunks`, drives the dispatched specialist to emit claims in-turn, and passes them here for per-kind cypher emission. Applies the durable rejection sidecar so already-triaged claims do not re-surface. Read-only: the per-row operator gate runs in the skill, this tool never writes.", {
|
|
569
|
+
lifelineTool(server, "conversation-archive-derive-insights", "Convert one chunk's agent-produced claims into operator-facing claim proposals (mention, task, preference, note, observed-relationship). The conversation-archive-enrich skill reads chunk bodies via `conversation-archive-list-chunks`, drives the dispatched specialist to emit claims in-turn, and passes them here for per-kind cypher emission. Applies the durable rejection sidecar so already-triaged claims do not re-surface. Read-only: the per-row operator gate runs in the skill, this tool never writes.", {
|
|
570
570
|
archiveElementId: z
|
|
571
571
|
.string()
|
|
572
572
|
.describe("elementId of the :ConversationArchive parent (Task 397 — keyed on conversationIdentity) the operator named (required)."),
|