@rubytech/create-maxy-code 0.1.299 → 0.1.301
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/plugins/admin/PLUGIN.md +10 -10
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +84 -84
- package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +4 -4
- package/payload/platform/plugins/aeo/PLUGIN.md +1 -1
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +4 -4
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +43 -22
- package/payload/platform/plugins/cloudflare/references/reset-guide.md +19 -1
- package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +19 -3
- package/payload/platform/plugins/contacts/PLUGIN.md +1 -1
- package/payload/platform/plugins/docs/references/admin-identity-gate.md +6 -6
- package/payload/platform/plugins/docs/references/admin-session.md +15 -15
- package/payload/platform/plugins/docs/references/admin-ui.md +18 -18
- package/payload/platform/plugins/docs/references/aeo.md +1 -1
- package/payload/platform/plugins/docs/references/deployment.md +3 -3
- package/payload/platform/plugins/docs/references/internals.md +28 -28
- package/payload/platform/plugins/docs/references/platform.md +21 -21
- package/payload/platform/plugins/docs/references/plugins-guide.md +3 -3
- package/payload/platform/plugins/docs/references/troubleshooting.md +6 -6
- package/payload/platform/plugins/docs/references/visitor-graph.md +3 -3
- package/payload/platform/plugins/email/PLUGIN.md +2 -2
- package/payload/platform/plugins/email/references/email-reference.md +2 -2
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +2 -2
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +9 -9
- package/payload/platform/plugins/memory/references/graph-primitives.md +2 -2
- package/payload/platform/plugins/memory/references/schema-base.md +14 -14
- package/payload/platform/plugins/memory/references/transcript-formats/circleback.md +1 -1
- package/payload/platform/plugins/memory/references/transcript-formats/otter.md +1 -1
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +6 -6
- package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +1 -1
- package/payload/platform/plugins/memory/skills/conversation-archive-mcp/SKILL.md +8 -8
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +5 -5
- package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/daily-notes.md +1 -1
- package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/SKILL.md +1 -1
- package/payload/platform/plugins/slides/PROVENANCE.md +1 -1
- package/payload/platform/plugins/substack-import/skills/substack-import/SKILL.md +1 -1
- package/payload/platform/plugins/telegram/PLUGIN.md +1 -1
- package/payload/platform/plugins/url-get/PLUGIN.md +2 -2
- package/payload/platform/plugins/whatsapp/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/whatsapp/PLUGIN.md +5 -5
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +5 -5
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +1 -1
- package/payload/platform/plugins/workflows/PLUGIN.md +1 -1
- package/payload/platform/plugins/x-import/PLUGIN.md +2 -2
- package/payload/platform/plugins/x-import/skills/x-import/SKILL.md +1 -1
- package/payload/platform/scripts/__tests__/check-no-task-id-leaks.test.sh +125 -0
- package/payload/platform/scripts/__tests__/resume-tunnel.test.sh +253 -0
- package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
- package/payload/platform/scripts/check-no-task-id-leaks.mjs +54 -6
- package/payload/platform/scripts/resume-tunnel.sh +89 -5
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +14 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +11 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts +4 -0
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +54 -3
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +18 -0
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +137 -0
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +1 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +1 -1
- package/payload/platform/templates/specialists/agents/librarian.md +2 -2
- package/payload/server/{chunk-7H4KMTMC.js → chunk-JHSF5VPE.js} +171 -95
- package/payload/server/maxy-edge.js +19 -1
- package/payload/server/public/assets/AdminShell-BJrxifYa.js +1 -0
- package/payload/server/public/assets/{Checkbox-DHnspkuk.js → Checkbox-C7bBRzLP.js} +1 -1
- package/payload/server/public/assets/{Transcript-9FlneAzM.js → Transcript-bGy_Gx99.js} +1 -1
- package/payload/server/public/assets/admin-jYKA7jR5.js +1 -0
- package/payload/server/public/assets/{arc-CPOhuw1V.js → arc-DxSm8tli.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-6g9jRVgc.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-BaR37f-j.js → architectureDiagram-Q4EWVU46-7JSAh0vL.js} +1 -1
- package/payload/server/public/assets/audio-attachment-mime-DL5R1M2A.js +30 -0
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-Du7NMQfQ.js → blockDiagram-DXYQGD6D-DLoVmHKD.js} +1 -1
- package/payload/server/public/assets/{browser--bCCzvRh.js → browser-7fyPCFWB.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-F3GnFLbs.js → c4Diagram-AHTNJAMY-Nv1G0o9P.js} +1 -1
- package/payload/server/public/assets/channel-BgQLJanG.js +1 -0
- package/payload/server/public/assets/chat-UF9ATDwe.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-SUv9UlAA.js → chunk-2KRD3SAO-BpqPMhoA.js} +1 -1
- package/payload/server/public/assets/chunk-336JU56O-DgXHlVU_.js +2 -0
- package/payload/server/public/assets/chunk-426QAEUC-Dr_XVuUq.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-D5iKMyHO.js → chunk-4BX2VUAB-nwyZSZH8.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-BahbQ7-Z.js → chunk-4TB4RGXK-Tfxz9LHX.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-DfF9myIx.js → chunk-55IACEB6-6FMJQGXG.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-jF6a9UVe.js → chunk-5FUZZQ4R-g2FgZF3D.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-BOCI-roy.js → chunk-5PVQY5BW-DtLwXsPH.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-E5wGK0dP.js → chunk-67CJDMHE-K91CP5ZU.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-CtT0GsUT.js → chunk-7N4EOEYR-BGpCQhqK.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-CGUj8eo-.js → chunk-AA7GKIK3-jkDbInck.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-OPAIjvSk.js → chunk-BSJP7CBP-mDosdzGs.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-S_D8FhIT.js → chunk-CIAEETIT-DhBxewpQ.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-CkD4AyZi.js → chunk-EDXVE4YY-B7c-9Emg.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-CmoK-c5f.js → chunk-ENJZ2VHE-Cqhhncox.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-DcyogdNf.js → chunk-FMBD7UC4-BpDq6wj1.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-8iNobZdv.js → chunk-FOC6F5B3-Ds02-ktu.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-DeGZE9pw.js → chunk-ICPOFSXX-BTX5POFr.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-CkoKFdgU.js → chunk-K5T4RW27-Du1PXXBU.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-DyNP1efP.js → chunk-KGLVRYIC-siasOAf8.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-WZPpo5Dd.js → chunk-LIHQZDEY-xf1rbZtf.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-Cr7E0-Df.js → chunk-ORNJ4GCN-DeTLQ_LD.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-BPjxo1Oc.js → chunk-OYMX7WX6-DQ7_oVJn.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-DF4o9HRJ.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-CuDS7yVX.js → chunk-U2HBQHQK-CXmFUKgn.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-DEkde3xe.js → chunk-X2U36JSP-Bj8-8Wkz.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-BwRCuKyu.js → chunk-XPW4576I-Con3TXqt.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-DD9nUp-w.js → chunk-YZCP3GAM-Dx8BHGWf.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-BDH9b11G.js → chunk-ZZ45TVLE-Dp4Q5Y-f.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DAbPKU6u.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BKuFQLcv.js +1 -0
- package/payload/server/public/assets/clone-DnNHGhNe.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-CfeophtW.js → cose-bilkent-S5V4N54A-pZiCv69E.js} +1 -1
- package/payload/server/public/assets/{dagre-CVxEmku2.js → dagre-CLqhEgbL.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-TOuVLdjq.js → dagre-KV5264BT-BCxE8iyk.js} +1 -1
- package/payload/server/public/assets/{data-B8Waqy4Q.js → data-CIJnkfgX.js} +1 -1
- package/payload/server/public/assets/{diagram-5BDNPKRD-BJQnfJjx.js → diagram-5BDNPKRD-slUEdZzz.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-CqK94J29.js → diagram-G4DWMVQ6-DPcraMfu.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-BCnATv2X.js → diagram-MMDJMWI5-DR6luhGK.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-Cl3BUrVs.js → diagram-TYMM5635-Jap1B4wA.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA--Uw0kvib.js → erDiagram-SMLLAGMA-DJKUs2hO.js} +1 -1
- package/payload/server/public/assets/{file-download-Pk6cNtuB.js → file-download-gth6Gpmn.js} +1 -1
- package/payload/server/public/assets/{flatten-DtM0XAMt.js → flatten-Cl1Bc3dR.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-B_VGZIbJ.js → flowDiagram-DWJPFMVM-C8W-ZZ9W.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-D-vdf1dC.js → ganttDiagram-T4ZO3ILL-DYCX4Xu2.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-g7mvu6IY.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CpyAlvu3.js → gitGraphDiagram-UUTBAWPF-CZu_fpgS.js} +1 -1
- package/payload/server/public/assets/{graph-D-VQpkaS.js → graph-DHnB3BHK.js} +1 -1
- package/payload/server/public/assets/{graph-labels-cJvlyVxD.js → graph-labels-huk-fKcY.js} +1 -1
- package/payload/server/public/assets/{graphlib-DaV4XyBp.js → graphlib-Bdp7TA4y.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-DZqJuIkB.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-cGfGccHz.js +2 -0
- package/payload/server/public/assets/{isEmpty-D__iH1WQ.js → isEmpty-D1pGOD1J.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-_4NvfwwU.js → ishikawaDiagram-UXIWVN3A-B7KfIX6z.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-PJQ-tPWv.js → journeyDiagram-VCZTEJTY-CSDwBfhF.js} +1 -1
- package/payload/server/public/assets/jsx-runtime-DZddXDvJ.css +1 -0
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-asCNlyH4.js → kanban-definition-6JOO6SKY-CWzcEKTr.js} +1 -1
- package/payload/server/public/assets/{line-BSXr2IPU.js → line-BNQSlbYn.js} +1 -1
- package/payload/server/public/assets/{linear-CSAKvvko.js → linear-BN6kGN93.js} +1 -1
- package/payload/server/public/assets/mermaid-parser.core-CcFhkElq.js +4 -0
- package/payload/server/public/assets/mermaid.core-BsIeJ7Cc.js +11 -0
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-IxwnMT2Q.js → mindmap-definition-QFDTVHPH-CQVzj6D_.js} +1 -1
- package/payload/server/public/assets/operator-D3QAzlPs.js +1 -0
- package/payload/server/public/assets/{ordinal-BRQ5CzZY.js → ordinal-CBZeH4Yp.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-Hn8rlHpy.js +1 -0
- package/payload/server/public/assets/page-86edaOS1.js +1 -0
- package/payload/server/public/assets/pie-ZZUOXDRM-BPGWVqBm.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-DFqSnPPB.js → pieDiagram-DEJITSTG-DnrQ9fyn.js} +1 -1
- package/payload/server/public/assets/preload-helper-CQ36nxok.js +1 -0
- package/payload/server/public/assets/public-B4LqL6lA.js +6 -0
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-1AB3hlEb.js → quadrantDiagram-34T5L4WZ-BQWlCyWi.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-DMdLrj3E.js +1 -0
- package/payload/server/public/assets/{reduce-CkcXOmmJ.js → reduce-PuPlFPRX.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-C2Zpzq3l.js → requirementDiagram-MS252O5E-Du_vZhU1.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DkmhPhwS.js → sankeyDiagram-XADWPNL6-Cf6Z8GPQ.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-1hQLpeZ1.js → sequenceDiagram-FGHM5R23-CS8GVol8.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-CBHIwm3f.js → stateDiagram-FHFEXIEX-Bv1NW2F1.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BGUCxPmp.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-CLRWWipW.js → timeline-definition-GMOUNBTQ-ClGYAsr0.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CWxofUbI.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-Cfgb_ZG0.js +1 -0
- package/payload/server/public/assets/{useSelectionMode-6fff2UIV.js → useSelectionMode-lrH0qwAR.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-ByFoQMek.js → vennDiagram-DHZGUBPP-C2wTcxQT.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-B45olYjj.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-DIkXRSha.js → wardleyDiagram-NUSXRM2D-D86-ivEx.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BNdEcN73.js → xychartDiagram-5P7HB3ND-Ba5WAN8P.js} +1 -1
- package/payload/server/public/browser.html +6 -6
- package/payload/server/public/chat.html +9 -8
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +9 -9
- package/payload/server/public/operator.html +9 -9
- package/payload/server/public/public.html +7 -6
- package/payload/server/server.js +164 -124
- package/payload/server/public/assets/AdminShell-Eo1RXZxq.js +0 -1
- package/payload/server/public/assets/admin-ORJGvOqo.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-CQHotPO2.js +0 -1
- package/payload/server/public/assets/audio-attachment-mime-CFRERhV0.js +0 -1
- package/payload/server/public/assets/channel-DfO_IgSU.js +0 -1
- package/payload/server/public/assets/chat-DIrzZjdO.js +0 -1
- package/payload/server/public/assets/chunk-336JU56O-DHUrM3QG.js +0 -2
- package/payload/server/public/assets/chunk-426QAEUC-CBo-Lx7X.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-CNlL2tiW.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-Bo-iLGpk.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Bz7DNpvQ.js +0 -1
- package/payload/server/public/assets/clone-Bh_bM_44.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-Ds2hoRX4.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-BuX4gDTt.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-pcFw8-8F.js +0 -2
- package/payload/server/public/assets/jsx-runtime-BjFkEGXb.css +0 -1
- package/payload/server/public/assets/mermaid-parser.core-DFxv-h16.js +0 -4
- package/payload/server/public/assets/mermaid.core-Be34BFtA.js +0 -11
- package/payload/server/public/assets/operator-DyLRFmNE.js +0 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-9qQ2zAJ3.js +0 -1
- package/payload/server/public/assets/page-CFgywgGE.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-aDbmozld.js +0 -1
- package/payload/server/public/assets/public-_pD6yEQ6.js +0 -35
- package/payload/server/public/assets/radar-PYXPWWZC-CEm-iLey.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DHGLsXgi.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-DYa2gPJa.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-D816ED1y.js +0 -1
- package/payload/server/public/assets/wardley-RL74JXVD-BniDWDlr.js +0 -1
- /package/payload/server/public/assets/{_baseFor-SRtUHe7G.js → _baseFor-brRjpUOX.js} +0 -0
- /package/payload/server/public/assets/{array-BpsnM-Py.js → array-BGFCBI0e.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-D4Rl1H5h.js → cytoscape.esm-TvRJ2tGX.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-C7sQPl-O.js → defaultLocale-CRZydyG6.js} +0 -0
- /package/payload/server/public/assets/{dist-BXJR8AkG.js → dist-Dbh7HrZX.js} +0 -0
- /package/payload/server/public/assets/{init-BydxaDEV.js → init-B8gtcn7T.js} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-B54kfC3e.js → jsx-runtime-CMu8uN0-.js} +0 -0
- /package/payload/server/public/assets/{katex-B0fVeDx4.js → katex-RxgSu_dy.js} +0 -0
- /package/payload/server/public/assets/{path-jlWYQ2i9.js → path-DZF-JdEe.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
- /package/payload/server/public/assets/{src-CPkr6SrY.js → src-BoaldmnP.js} +0 -0
|
@@ -39,13 +39,13 @@ When loading this reference, confirm which schema files were consulted by noting
|
|
|
39
39
|
| Position | `Position` | platform-native (analogue of `schema:OrganizationRole`) | — | `accountId`, `title`, `startDate` |
|
|
40
40
|
| WhatsApp Conversation (legacy) | `WhatsAppConversation` | extends `schema:Conversation` | — | `accountId`, `sessionId`, `archiveSourceFile`, `firstMessageAt`, `lastMessageAt`, `participantCount`, `messageCount`, `scope`, `createdByAgent`, `createdBySession`, `createdAt` |
|
|
41
41
|
| WhatsApp Message (legacy) | `WhatsAppMessage` | extends `schema:Message` | — | `accountId`, `sessionId`, `messageId`, `dateSent`, `body`, `senderName`, `sequenceIndex`, `scope`, `createdByAgent`, `createdBySession`, `createdAt` |
|
|
42
|
-
| Email thread | `ConversationArchive {source:'email'}` | One archive per thread (
|
|
42
|
+
| Email thread | `ConversationArchive {source:'email'}` | One archive per thread (the `:Email` label was retired; threads moved off `:KnowledgeDocument` onto the source-agnostic conversation-archive pipeline) | RFC 5322 over IMAP | `accountId`, `conversationIdentity` (sha256 of accountId + sortedParticipantElementIds + source), `source = 'email'`, `title` (subject of root message), `firstMessageAt`, `lastMessageAt`, `lastIngestedMessageHash`, `authorshipMode`. `:Section` chunks carry the message bodies via `HAS_SECTION` and the `NEXT` chain. |
|
|
43
43
|
| SocialPost | `SocialPost` | `schema:SocialMediaPosting` | — | `accountId`, `platform` (linkedin/twitter/instagram/…), `body`, `datePublished`, `authorshipMode` |
|
|
44
|
-
| VoiceProfile | `VoiceProfile` | platform-native (
|
|
45
|
-
| VoiceEdit | `VoiceEdit` | platform-native (
|
|
44
|
+
| VoiceProfile | `VoiceProfile` | platform-native (authorial-voice style card) | — | `accountId`, `adminUserId`, `styleCard`, `generatedAt`, `corpusSize`, `feedbackEntries` |
|
|
45
|
+
| VoiceEdit | `VoiceEdit` | platform-native (operator edit on an agent draft) | — | `accountId`, `adminUserId`, `originalText`, `editedText`, `intent`, `occurredAt` |
|
|
46
46
|
| AEO Audit | `AEOAudit` | platform-native (AEO audit history) | — | `accountId`, `auditId`, `target`, `score`, `heuristicsJson`, `runAt`, `createdByAgent`, `createdBySession`, `createdAt` |
|
|
47
47
|
| Idea | `Idea` | platform-native (original-thinking candidate extracted by the pre-turn graph pass) | — | `accountId`, `body`, `sessionId`, `scope`, `createdByAgent`, `createdBySession`, `createdAt` |
|
|
48
|
-
| TimelineEvent | `TimelineEvent` | platform-native (
|
|
48
|
+
| TimelineEvent | `TimelineEvent` | platform-native (compiled-truth + timeline doctrine) | — | `accountId`, `timelineEventId`, `occurredAt`, `kind`, `summary`, `source` |
|
|
49
49
|
|
|
50
50
|
**Branding properties on LocalBusiness:** `primaryColor`, `accentColor`, `backgroundColor`, `tagline` — optional, used to brand the public chat endpoint. Hex color values must match `#[0-9a-fA-F]{3,8}`. Logo and icon are linked via `HAS_BRAND_ASSET → ImageObject` with `purpose: "logo"` or `"icon"`.
|
|
51
51
|
|
|
@@ -53,7 +53,7 @@ When loading this reference, confirm which schema files were consulted by noting
|
|
|
53
53
|
|
|
54
54
|
**Personal-profile role on Person:** the optional `role` property on a `Person` node carries the value `"admin-personal"` when the node is the operator's personal-profile bootstrap (onboarding step 9 personal mode). The platform accepts `Person {role: "admin-personal"}` in lieu of a `LocalBusiness` so personal-mode users can complete onboarding without registering a business. Do not set `role: "admin-personal"` on customer or staff Person nodes — it's the personal-profile discriminator and only the bootstrap node should carry it.
|
|
55
55
|
|
|
56
|
-
**Organization is open by default.** The label covers both supplier-style use (a third-party company you do business with, where vCard-shaped contact data is meaningful) and reference-style use (an organization named in a CV, bio, or document where contact info is unknown or irrelevant). The validator enforces only `accountId` and `name`. For supplier-style use, gather `telephone` or `email` and a postal address when the source provides them — agent judgment, not validator-enforced.
|
|
56
|
+
**Organization is open by default.** The label covers both supplier-style use (a third-party company you do business with, where vCard-shaped contact data is meaningful) and reference-style use (an organization named in a CV, bio, or document where contact info is unknown or irrelevant). The validator enforces only `accountId` and `name`. For supplier-style use, gather `telephone` or `email` and a postal address when the source provides them — agent judgment, not validator-enforced. The row previously enforced `telephone or email` and the agent-write path obeyed it, but the document-ingest writer bypassed the validator and minted 23 contact-less Organizations on the first install that combined a CV PDF with a website ingest; those rows were schema-invalid against the prose contract while being valid against the actual write surface. Relaxing the rule resolves the straddle: the agent decides per case whether contacts belong, and search no longer hides ingest-created Orgs.
|
|
57
57
|
|
|
58
58
|
**Person is open by default.** Any Person property the agent judges useful is permitted; the set is comprehensive, not enumerated. The only hard requirement is `accountId` (the universal account-scoping property — see [feedback_accountid_must_be_uuid.md](../../../../.claude/projects/-Users-neo-getmaxy/memory/feedback_accountid_must_be_uuid.md)). Identifying fields (`givenName`, `familyName`, `email`, `telephone`, …) are agent-judged: a single field is enough to create a Person; the agent and operator manage dedup and conflicts downstream. Forbidden Properties (e.g. `Person.name` rejects in favour of `givenName` + `familyName`) and Schema.org synonyms (e.g. `phone` rejects in favour of `telephone`) still apply. The `profile-update` tool's `personFields` parameter is the personal-profile conversational surface, validating in `mode: "update"` so SET-only writes skip the (now-empty) required-property check (givenName/familyName were established at PIN setup).
|
|
59
59
|
|
|
@@ -63,9 +63,9 @@ When loading this reference, confirm which schema files were consulted by noting
|
|
|
63
63
|
|
|
64
64
|
**Position** carries a single role held by a `UserProfile` at an `Organization` — its title, dates, and per-role properties (e.g. achievement bullets in `description`, `endDate` for a finished role). Each role is its own node so it can carry its own structure; multiple Positions per UserProfile (career history); one Organization may host many Positions across many UserProfiles. Written by the `document-ingest` skill when a CV section classifies as `Position`. The natural pattern is `(:UserProfile)-[:HAS_POSITION]->(:Position)-[:AT]->(:Organization)` — never set the employer name as a property on Position; always create the AT edge to the (MERGEd) Organization.
|
|
65
65
|
|
|
66
|
-
**`authorshipMode` on content labels
|
|
66
|
+
**`authorshipMode` on content labels.** Every `:KnowledgeDocument`, `:ConversationArchive` (every chat transcript parent, including email threads under `source:'email'`), `:Message`, `:SocialPost`, and `:Section` write carries an `authorshipMode` enum drawn from `human-only | human-led-agent-assisted | agent-led-human-reviewed | agent-only | unknown`. The value records who actually wrote the content body, not who triggered the write. `human-only` means every word of the body came from the operator; `human-led-agent-assisted` is operator-authored with light agent polish (the operator chose every claim and structure); `agent-led-human-reviewed` is agent-drafted then operator-edited before send; `agent-only` is pure agent output the operator did not touch. The voice-mirror distillation walks the corpus where `authorshipMode IN ['human-only', 'human-led-agent-assisted']` — both modes carry voice signal. `:ConversationArchive` `:Section` chunks (WhatsApp / Telegram / Signal / Granola / Otter / Circleback exports ingested under `(:ConversationArchive)-[:HAS_SECTION]->(:Section)`) are first-class voice corpus and the highest-leverage signal source because chat captures the unguarded voice in a way email rarely does. Legacy nodes default to `unknown` until reclassified via the voice-mirror backfill flow. The per-label authorshipMode index on `:Section` covers transcript chunks without a secondary label. Writers that cannot determine the mode (e.g. bulk archive imports) write `unknown` rather than guessing.
|
|
67
67
|
|
|
68
|
-
**Voice profile pattern
|
|
68
|
+
**Voice profile pattern.** `(:AdminUser)-[:HAS_VOICE_PROFILE]->(:VoiceProfile)`. The styleCard is YAML serialized to a string property containing sentence-length distribution, register, favoured/avoided constructions and words, taboos, opening/closing patterns, and exemplar sentences. `(:VoiceProfile)-[:LEARNED_FROM]->(:KnowledgeDocument|:Message|:SocialPost|:Section)` links the profile to every node in its source corpus so the operator can audit what drove the distillation. Each operator edit on an agent draft becomes `(:VoiceEdit)-[:FEEDBACK_FOR]->(:VoiceProfile)` with the original/edited bodies plus a Haiku-summarised `intent` so the next distillation knows what to adjust.
|
|
69
69
|
|
|
70
70
|
**Idea** is a forward-only write surface for original-thinking candidates extracted from admin-side conversations by `database-operator` per-write briefs (dispatched by admin inline mid-turn, and during the on-demand `/insight` graph-completeness sweep). `body` is the verbatim operator statement; `sessionId` denormalises the source conversation; `scope` is always `"admin"`. Never written by any other path. No dedup or clustering at write time — let the brain accumulate first.
|
|
71
71
|
|
|
@@ -73,7 +73,7 @@ When loading this reference, confirm which schema files were consulted by noting
|
|
|
73
73
|
|
|
74
74
|
---
|
|
75
75
|
|
|
76
|
-
## Compiled truth + timeline
|
|
76
|
+
## Compiled truth + timeline
|
|
77
77
|
|
|
78
78
|
Every `:Person`, `:Organization`, and `:Concept` carries a two-part knowledge shape: a **compiled truth** above the rule (the agent's current best understanding of the subject, rewritten when evidence changes) and an **append-only timeline** below (dated events linked via `:HAS_TIMELINE_EVENT`, never edited).
|
|
79
79
|
|
|
@@ -83,11 +83,11 @@ Every `:Person`, `:Organization`, and `:Concept` carries a two-part knowledge sh
|
|
|
83
83
|
| `compiledTruthUpdatedAt` | datetime | Last rewriter or operator write. Epoch on first write. |
|
|
84
84
|
| `compiledTruthSource` | `'rewriter' \| 'operator'` | Last writer. `'operator'` marks passes shaped by an operator hint from chat; the rewriter respects a 7-day cooldown against further auto-rewriter passes unless `forceRewrite=true` is set. |
|
|
85
85
|
|
|
86
|
-
The compiled truth is system-managed: direct writes to any of the three properties via `memory-update` or `memory-write` are **rejected loudly** for normal callers.
|
|
86
|
+
The compiled truth is system-managed: direct writes to any of the three properties via `memory-update` or `memory-write` are **rejected loudly** for normal callers. The rewriter runs in the dispatched `compiled-truth-rewriter` specialist, which is the only sanctioned writer. The admin agent surfaces an operator request (e.g. "refresh Acme's compiled truth", or "fix the bio for Joel — say he's an angel investor now") by dispatching the specialist via the Task tool with the `nodeId`, an optional `operatorHint` (admin-twin correction), and an optional `publicOperatorHint` (public-twin correction). The specialist reads the node + 90-day timeline, computes the new summary, and writes it back via `memory-update`; the gate allows the write because `MAXY_SPECIALIST=compiled-truth-rewriter` is stamped on its env at spawn time.
|
|
87
87
|
|
|
88
|
-
### Revision history
|
|
88
|
+
### Revision history
|
|
89
89
|
|
|
90
|
-
Existing `:CompiledTruthRevision` rows remain readable via `memory-compiled-truth-history(nodeId)`; the dream-cycle phase 5 prune (keep newest 20 per entity) still runs.
|
|
90
|
+
Existing `:CompiledTruthRevision` rows remain readable via `memory-compiled-truth-history(nodeId)`; the dream-cycle phase 5 prune (keep newest 20 per entity) still runs. The inline single-TX revision stamp that used to fire on every rewriter SET was deleted. Reinstatement under the new specialist path is tracked as a follow-up; until then, new specialist writes do not extend the revision chain.
|
|
91
91
|
|
|
92
92
|
### Timeline events
|
|
93
93
|
|
|
@@ -101,7 +101,7 @@ Existing `:CompiledTruthRevision` rows remain readable via `memory-compiled-trut
|
|
|
101
101
|
mentioned | met | quoted | attended | transacted
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
The extractor refuses on ambiguity rather than collapsing to "other". Default on write is `mentioned`; the typed-edge post-write hook
|
|
104
|
+
The extractor refuses on ambiguity rather than collapsing to "other". Default on write is `mentioned`; the typed-edge post-write hook upgrades the kind asynchronously when it can.
|
|
105
105
|
|
|
106
106
|
### Write doctrine
|
|
107
107
|
|
|
@@ -237,7 +237,7 @@ The closed enumeration:
|
|
|
237
237
|
|
|
238
238
|
`Preface`, `Abstract` (alias for Summary / ExecutiveSummary), `Introduction`, `TableOfContents`, `Chapter`, `Conclusion`, `Appendix`, `Bibliography` (alias for References), `Glossary`, `Acknowledgments` — each becomes `:Section:<Kind>` linked to the document via `(:KnowledgeDocument)-[:HAS_SECTION]->` and chained to siblings via `(:Section)-[:NEXT]->(:Section)` in reading order.
|
|
239
239
|
|
|
240
|
-
`Conversation` — chat-mode chunks emitted by `memory-classify` with `mode='chat'`. Becomes `:Section` linked via `(:ConversationArchive)-[:HAS_SECTION]->`
|
|
240
|
+
`Conversation` — chat-mode chunks emitted by `memory-classify` with `mode='chat'`. Becomes `:Section` linked via `(:ConversationArchive)-[:HAS_SECTION]->` — same Section shape as document-mode, only the parent label differs. The parent `:ConversationArchive` carries `conversationIdentity` (and `source`); readers that need conversation-vs-document filtering match on the parent label rather than a property heuristic. Properties beyond the base section shape: `summary`, `keywords`, `firstMessageAt`, `lastMessageAt`, `participantNames`, `messageCount`, `archiveSha256`. The `:Section` validator rules (embedding, body, position) apply uniformly.
|
|
241
241
|
|
|
242
242
|
**Contract-clause kinds (HAS_SECTION + NEXT, plus special-case extras for two kinds):**
|
|
243
243
|
|
|
@@ -289,7 +289,7 @@ Patterns with high occurrence count are candidates for new section-kind labels.
|
|
|
289
289
|
|
|
290
290
|
---
|
|
291
291
|
|
|
292
|
-
## Visitor Graph
|
|
292
|
+
## Visitor Graph
|
|
293
293
|
|
|
294
294
|
Behavioural analytics — pixel events from operator-owned listing pages land here.
|
|
295
295
|
Cross-session attribution is via a signed-token cookie that the property-recommender
|
|
@@ -35,7 +35,7 @@ A Circleback meeting object carries the following fields relevant to ingest:
|
|
|
35
35
|
| `attendees[]` — each with `name`, `email` | Per-attendee resolution against `:AdminUser` / `:Person` (Step 4 of the skill) |
|
|
36
36
|
| `transcript[]` — each with `speaker`, `timestamp`, `text` | Rendered into turn-attributed `[<ISO>] <speaker>: <text>` lines for the typed-section JSON |
|
|
37
37
|
| `calendarEventId` / `event_id` | `calendarEventId` cross-reference property on `:ConversationArchive` when present |
|
|
38
|
-
| `actionItems[]` / `summary` | NOT used for `:Section` rows — provider-authored layers; downstream Phase 2 derivation
|
|
38
|
+
| `actionItems[]` / `summary` | NOT used for `:Section` rows — provider-authored layers; downstream Phase 2 derivation produces uniform typed claims from the transcript |
|
|
39
39
|
|
|
40
40
|
## Edge cases
|
|
41
41
|
|
|
@@ -42,7 +42,7 @@ An Otter conversation object carries the following fields relevant to ingest:
|
|
|
42
42
|
- **Speaker labels are pseudonymous** ("Speaker 1", "Speaker 2") when Otter could not identify a participant. Surface them to the operator at attendee-resolution time — the operator either matches "Speaker 1" to a known `:Person` (because they recognise the meeting) or blocks the attendee.
|
|
43
43
|
- **Live in-progress conversations** — Otter may expose a conversation that is still being transcribed. Skip these: the skill only ingests finished recordings. Detect by checking the provider's `status` / `is_live` field when present and FAIL with `phase=fetch reason="otter conversation <id> still in progress"`.
|
|
44
44
|
- **Attendee with no email** (free-text participant added in Otter): falls through to the per-attendee prompt loop.
|
|
45
|
-
- **Highlights / action items** in Otter's UI are provider-authored layers; do not ingest them as separate `:Section` rows. The transcript text is the source of truth; downstream Phase 2 derivation produces typed claims uniformly across sources
|
|
45
|
+
- **Highlights / action items** in Otter's UI are provider-authored layers; do not ingest them as separate `:Section` rows. The transcript text is the source of truth; downstream Phase 2 derivation produces typed claims uniformly across sources.
|
|
46
46
|
|
|
47
47
|
## What this reference is not
|
|
48
48
|
|
|
@@ -7,17 +7,17 @@ description: Source-agnostic ingest for conversation transcripts. One skill for
|
|
|
7
7
|
|
|
8
8
|
**Invoked from `specialists:librarian`** — the admin agent dispatches the librarian when the operator drops a conversation transcript; the librarian loads this skill.
|
|
9
9
|
|
|
10
|
-
**Default parent.** Every `:ConversationArchive` (
|
|
10
|
+
**Default parent.** Every `:ConversationArchive` (previously a `:KnowledgeDocument` carrying `conversationIdentity`) is anchored to the operator-confirmed owner (`:AdminUser` for the account owner, `:Person` for a third-party archive). The owner's elementId is derived inside the bash entry from `(ACCOUNT_ID, USER_ID)` env (see Identity below) and returned in the prepare JSON; it becomes the `anchorNodeId` argument to `memory-ingest` for every session of the run. Per-session `:Section` chunks attach to the archive via `:HAS_SECTION`, chained in reading order by `:NEXT`. Participant cross-edges from the archive to each distinct sender (`:Person` / `:AdminUser`) attach via `:PARTICIPANT_IN` — they describe the conversation, they do not substitute for the parent.
|
|
11
11
|
|
|
12
12
|
One skill, one bash entry, one writer. The pipeline is identical for every conversation source. The only source-specific code is the per-source normaliser (a pluggable function under [`platform/plugins/memory/mcp/src/lib/conversation-normalisers/`](../../mcp/src/lib/conversation-normalisers/)). New sources = a new normaliser file + an enum entry, never a new skill.
|
|
13
13
|
|
|
14
|
-
## Where the work lives
|
|
14
|
+
## Where the work lives
|
|
15
15
|
|
|
16
16
|
The deterministic work — parsing, sessionising, archiveSha256/conversationIdentity, prior-cursor lookup, delta slicing — lives in the bash entry and runs once per ingest. The LLM-judgment work — producing topic-bounded `:Section` chunks with summary + keywords for each session — lives in the dispatched specialist's per-turn work, one session at a time, against the turn-attributed text the prepare script returns. The specialist calls the `memory-ingest` MCP tool once per session with that session's classified chunks + the session's per-session cursor; the writer commits chunks + cursor advance in one Cypher transaction, so a kill mid-archive resumes from the next session on re-issue without re-classifying anything already written.
|
|
17
17
|
|
|
18
18
|
## Confirmed sources (Phase 0)
|
|
19
19
|
|
|
20
|
-
`whatsapp` ships today (relocated grammar from the retired `whatsapp-import` plugin). `x-dm` ships alongside the `x-import` plugin
|
|
20
|
+
`whatsapp` ships today (relocated grammar from the retired `whatsapp-import` plugin). `x-dm` ships alongside the `x-import` plugin — driven by the x-import skill, one invocation per dmConversation slice. Other sources land as needed:
|
|
21
21
|
- `telegram` — Telegram JSON export
|
|
22
22
|
- `signal` — Signal text export
|
|
23
23
|
- `linkedin-messages` — LinkedIn DM thread export (LinkedIn *connections* stay flat-dataset; only DMs route here)
|
|
@@ -26,7 +26,7 @@ The deterministic work — parsing, sessionising, archiveSha256/conversationIden
|
|
|
26
26
|
- `imessage` — iMessage backup export
|
|
27
27
|
- `slack` — Slack channel export
|
|
28
28
|
- `x-dm` — X (Twitter) Direct Messages, dispatched by the `x-import` skill from `data/direct-messages.js` / `data/direct-messages-group.js` — one bash invocation per dmConversation slice; senders carry numeric X senderIds resolved to `:Person` / `:AdminUser` upstream in the x-import skill
|
|
29
|
-
- `email` — RFC 5322 email thread, dispatched by the `email` plugin's writer
|
|
29
|
+
- `email` — RFC 5322 email thread, dispatched by the `email` plugin's writer. Participants resolve to existing `:Person` / `:AdminUser` by the `email` property on the canonical node — closed-set, no auto-creation, same discipline as LinkedIn DMs.
|
|
30
30
|
|
|
31
31
|
Until a normaliser ships, the bash entry loud-fails with the implemented set listed in the error.
|
|
32
32
|
|
|
@@ -118,7 +118,7 @@ For each `session` in the prepare JSON, in order:
|
|
|
118
118
|
|
|
119
119
|
1. **Read** the session's `turnText` (turn-attributed `[ISO timestamp] Sender: body\n…`).
|
|
120
120
|
|
|
121
|
-
2. **Produce a typed-section JSON chunking in-turn.** Split the turn text into topic-bounded `:Section` chunks following the standard ontology shape: each section carries `kind` (`Conversation` for a generic chunk, or a typed kind like `Topic` when the conversation explicitly establishes a typed entity), `title`, `summary` (1-3 sentences, ≤500 chars), `sourceStart` / `sourceEnd` (char offsets into this session's `turnText`), `properties` (carry `firstMessageAt`, `lastMessageAt`, and `messageCount` from the session — or finer per-chunk slices when the chunking subdivides), and `anchorEdge: null` (chunks attach via the parent's `:HAS_SECTION` chain). **Do NOT emit `body`.** The writer slices `:Section.body` server-side from the bin's per-session tempfile using the `(sourceStart, sourceEnd)` offsets —
|
|
121
|
+
2. **Produce a typed-section JSON chunking in-turn.** Split the turn text into topic-bounded `:Section` chunks following the standard ontology shape: each section carries `kind` (`Conversation` for a generic chunk, or a typed kind like `Topic` when the conversation explicitly establishes a typed entity), `title`, `summary` (1-3 sentences, ≤500 chars), `sourceStart` / `sourceEnd` (char offsets into this session's `turnText`), `properties` (carry `firstMessageAt`, `lastMessageAt`, and `messageCount` from the session — or finer per-chunk slices when the chunking subdivides), and `anchorEdge: null` (chunks attach via the parent's `:HAS_SECTION` chain). **Do NOT emit `body`.** The writer slices `:Section.body` server-side from the bin's per-session tempfile using the `(sourceStart, sourceEnd)` offsets — this closes the same LLM-paraphrase vector closed on the document path. Any `body` you emit is logged and ignored. Also produce a `documentKeywords` array of 3-8 topic keywords for the session, deduplicated across the archive.
|
|
122
122
|
|
|
123
123
|
3. **Call the `memory-ingest` MCP tool** with this session's payload (conversation-archive path):
|
|
124
124
|
|
|
@@ -181,7 +181,7 @@ If the first session's response carries `cleanedPriorChunks > 0` and `prepare.re
|
|
|
181
181
|
- `MATCH (a:ConversationArchive { conversationIdentity: $cid }) RETURN elementId(a), a.source, a.lastIngestedMessageAt, a.lastIngestedMessageHash` — `source` matches `--source`; counters agree with the aggregated MCP responses.
|
|
182
182
|
- `MATCH (a:ConversationArchive { conversationIdentity: $cid })-[:HAS_SECTION]->(c:Section) RETURN count(c)` — equals the aggregated `sectionCount`.
|
|
183
183
|
- `MATCH (p)-[:PARTICIPANT_IN]->(:ConversationArchive { conversationIdentity: $cid }) RETURN count(p)` — equals `participantsLinked` after a first-ingest.
|
|
184
|
-
- Phase 2 (`:Observation` / `:Task` / `:Preference` derivation) is its own follow-up
|
|
184
|
+
- Phase 2 (`:Observation` / `:Task` / `:Preference` derivation) is its own follow-up.
|
|
185
185
|
|
|
186
186
|
## What this is not
|
|
187
187
|
|
|
@@ -9,7 +9,7 @@ description: "Walk an existing ConversationArchive's chunks and derive high-conf
|
|
|
9
9
|
|
|
10
10
|
One skill. Walks one `:ConversationArchive`'s chunks in pages. Per chunk, the specialist reads the chunk body and emits zero or more high-confidence claims under the four-kind contract below; the claims pass through `mcp__plugin_memory_memory__conversation-archive-derive-insights` for per-kind cypher emission; per proposal, the operator decides `wire / skip / reject`. Both tools are read-only — the only writes happen through this skill's per-row dispatch.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
The LLM step runs in the specialist's turn. There is no server-side Haiku round-trip.
|
|
13
13
|
|
|
14
14
|
## When to invoke
|
|
15
15
|
|
|
@@ -11,12 +11,12 @@ description: Operator-initiated, MCP-driven transcript ingest for Granola / Otte
|
|
|
11
11
|
|
|
12
12
|
**Shared writer surface.** Every meeting ingested through this skill lands as a `:ConversationArchive` indistinguishable in shape from a file-drop ingest of the same meeting:
|
|
13
13
|
|
|
14
|
-
- Parent label: `:ConversationArchive
|
|
14
|
+
- Parent label: `:ConversationArchive`.
|
|
15
15
|
- Identity: `conversationIdentity = sha256(accountId + ":" + sortedParticipantElementIds.join(",") + ":" + source)`, with `source ∈ {"granola", "otter", "circleback"}`.
|
|
16
16
|
- Section chunks attach via `:HAS_SECTION` chained by `:NEXT`, same as the file-drop path.
|
|
17
17
|
- Participant cross-edges via `:PARTICIPANT_IN` from the archive to each resolved `:Person` / `:AdminUser`.
|
|
18
18
|
- Per-provider stable meeting ID rides as a cross-reference property on the parent: `granolaNoteId`, `otterConversationId`, or `circlebackMeetingId`. Multiple may co-exist if the same conversationIdentity ingests through two providers in sequence (e.g. operator imports a meeting from Otter, then re-imports the same participant set from Granola — the second ingest MERGEs into the same parent and stamps the additional provider ID).
|
|
19
|
-
- `calendarEventId` stamped on the parent when the provider supplies it. The MERGE-into-`:Meeting` link is downstream of this skill —
|
|
19
|
+
- `calendarEventId` stamped on the parent when the provider supplies it. The MERGE-into-`:Meeting` link is downstream of this skill — a planned writer-side change uses `calendarEventId` as the primary MERGE key when present.
|
|
20
20
|
|
|
21
21
|
**No bash entry. No per-source normaliser.** The provider's MCP server is the parser. The skill calls `list` and `get` tools the provider exposes, renders the returned shape into turn-attributed text in-turn, and calls `memory-ingest` directly. This is the structural reason the skill is sibling rather than nested — there is no `conversation-archive-ingest.sh` involvement on this path.
|
|
22
22
|
|
|
@@ -97,7 +97,7 @@ For each selected meeting, in chronological order:
|
|
|
97
97
|
```
|
|
98
98
|
conversationIdentity = sha256(accountId + ":" + sortedParticipantElementIds.join(",") + ":" + source)
|
|
99
99
|
```
|
|
100
|
-
Source is one of `granola | otter | circleback`. The same formula as the file-drop path; a meeting that ingests once via this skill and is later re-exported via the file-drop path will MERGE into the same parent only if the file-drop source enum matches — which it will not (file-drop has its own source enum). Cross-provider MERGE within this skill (Granola + Otter recordings of the same Zoom call landing as one parent) requires the
|
|
100
|
+
Source is one of `granola | otter | circleback`. The same formula as the file-drop path; a meeting that ingests once via this skill and is later re-exported via the file-drop path will MERGE into the same parent only if the file-drop source enum matches — which it will not (file-drop has its own source enum). Cross-provider MERGE within this skill (Granola + Otter recordings of the same Zoom call landing as one parent) requires the planned writer-side change to use `calendarEventId` as the primary key when present. Until that lands, same-meeting two-provider ingest writes TWO `:ConversationArchive` rows — name this explicitly in the operator summary.
|
|
101
101
|
|
|
102
102
|
4. **Produce typed-section JSON chunking in-turn.** Split the rendered turn text into topic-bounded `:Section` chunks following the standard ontology shape: each section carries `kind` (`Conversation` for a generic chunk, or a typed kind like `Topic` when the conversation explicitly establishes a typed entity), `title`, `body`, `summary` (1-3 sentences, ≤500 chars), `sourceStart` / `sourceEnd` (char offsets into the rendered turn text), `properties` (`firstMessageAt`, `lastMessageAt`, `messageCount` from the section slice), and `anchorEdge: null`. Produce a `documentKeywords` array of 3-8 topic keywords for the meeting.
|
|
103
103
|
|
|
@@ -118,7 +118,7 @@ For each selected meeting, in chronological order:
|
|
|
118
118
|
- `scope` ← `"admin"`.
|
|
119
119
|
- **Additional properties on the parent (stamped via the standard property bag):**
|
|
120
120
|
- `granolaNoteId` | `otterConversationId` | `circlebackMeetingId` — set the one that matches the source.
|
|
121
|
-
- `calendarEventId` — set when the provider supplied one. Stamped now; the
|
|
121
|
+
- `calendarEventId` — set when the provider supplied one. Stamped now; the planned writer-side change will MATCH on this for cross-provider MERGE.
|
|
122
122
|
- `cleanupPriorChunks` ← `false` always on this path. The MCP path has no `--rebuild` semantics; a destructive re-ingest is the operator's call via the file-drop skill if ever needed.
|
|
123
123
|
|
|
124
124
|
6. **If `memory-ingest` returns an error**, stop the loop and surface the error. Meetings already ingested are durable on the parent's `:ConversationArchive` MERGE; re-running with the same filter resumes from the next un-ingested meeting (the empty-delta short-circuit on re-issue skips already-written meetings).
|
|
@@ -129,7 +129,7 @@ After the last meeting lands, emit three operator-facing lines aggregated across
|
|
|
129
129
|
|
|
130
130
|
1. **Fetch summary.** `Fetched <count> <provider> meeting(s) (<dateRange.first> → <dateRange.last>). Attendees: <distinct attendee names>.`
|
|
131
131
|
2. **Ingest summary.** `Ingested <count> :ConversationArchive row(s), <totalChunks> chunks total. Topics: <unioned documentKeywords>.`
|
|
132
|
-
3. **Cross-reference summary.** `Stamped provider IDs: <provider>=<count>. calendarEventId stamped on <n>/<total>. Cross-provider MERGE on calendarEventId pending
|
|
132
|
+
3. **Cross-reference summary.** `Stamped provider IDs: <provider>=<count>. calendarEventId stamped on <n>/<total>. Cross-provider MERGE on calendarEventId pending.`
|
|
133
133
|
|
|
134
134
|
For an empty-delta re-import (every meeting already ingested under the same `conversationIdentity` + same content hash): emit only line 1 + a noop line `noop reason="all <count> meetings already ingested (empty-delta on every conversationIdentity)"`.
|
|
135
135
|
|
|
@@ -152,7 +152,7 @@ For an empty-delta re-import (every meeting already ingested under the same `con
|
|
|
152
152
|
- **Never auto-create a `:Person`.** Email-autoresolve binds to existing nodes only. The operator owns every create.
|
|
153
153
|
- **One skill, not three.** Per-provider variance lives in the reference docs, not in three near-duplicate skills.
|
|
154
154
|
- **Provider OAuth lives in the host.** Maxy never holds a Granola / Otter / Circleback token. The operator runs `claude mcp add` and authorises each provider in the provider's own UI.
|
|
155
|
-
- **`calendarEventId` is the future primary MERGE key.** This skill stamps it; the writer-side MERGE-on-calendar-event change is
|
|
155
|
+
- **`calendarEventId` is the future primary MERGE key.** This skill stamps it; the writer-side MERGE-on-calendar-event change is planned. Until then, cross-provider dedup of the same meeting requires the operator to use a single provider per meeting.
|
|
156
156
|
|
|
157
157
|
## Verification (post-write)
|
|
158
158
|
|
|
@@ -166,5 +166,5 @@ For an empty-delta re-import (every meeting already ingested under the same `con
|
|
|
166
166
|
- **Not** a file-drop fallback for Granola / Otter / Circleband transcript exports. If a meeting falls outside the provider's MCP retention window, the operator's recourse today is to export the transcript file and feed it to the file-drop `conversation-archive` skill — that path requires a per-provider normaliser to be implemented under `conversation-archive`; not in scope here.
|
|
167
167
|
- **Not** an OAuth manager. Tokens live in Claude Code's MCP host, not in maxy.
|
|
168
168
|
- **Not** a polling sync. Every ingest is operator-initiated.
|
|
169
|
-
- **Not** the writer-side MERGE-on-`calendarEventId` change. This skill *stamps* `calendarEventId`; the writer-side change is
|
|
170
|
-
- **Not** an insight-extraction pass. Phase 2 derivation (`:Observation` / `:Task` / `:Preference` / `:MENTIONS`)
|
|
169
|
+
- **Not** the writer-side MERGE-on-`calendarEventId` change. This skill *stamps* `calendarEventId`; the writer-side change is planned.
|
|
170
|
+
- **Not** an insight-extraction pass. Phase 2 derivation (`:Observation` / `:Task` / `:Preference` / `:MENTIONS`) applies uniformly to every source.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: document-ingest
|
|
3
|
-
description: Universal document ingestion — maps any unstructured document (PDF, text, transcript, web page) OR chat archive (WhatsApp `_chat.txt`) to ontologically-grounded graph nodes. The dispatched specialist reads the cached extract and produces typed-section JSON in-turn from the loaded ontology, then calls memory-ingest (
|
|
3
|
+
description: Universal document ingestion — maps any unstructured document (PDF, text, transcript, web page) OR chat archive (WhatsApp `_chat.txt`) to ontologically-grounded graph nodes. The dispatched specialist reads the cached extract and produces typed-section JSON in-turn from the loaded ontology, then calls memory-ingest (the separate server-side memory-classify step was removed). Triggers when the operator uploads or fetches a document or chat export for ingestion. One skill for every input shape — no per-doctype, no per-channel branching.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Document Ingest
|
|
@@ -100,7 +100,7 @@ After extract returns, emit one line in chat: "Ingesting `<filename>` (`<N>` cha
|
|
|
100
100
|
|
|
101
101
|
### 2. Classify in-turn (your work)
|
|
102
102
|
|
|
103
|
-
You produce typed-section JSON yourself from the cached text and the loaded ontology. There is no classifier tool —
|
|
103
|
+
You produce typed-section JSON yourself from the cached text and the loaded ontology. There is no classifier tool — it was deleted. Read the relevant ontology references first (`platform/plugins/memory/references/schema-base.md` and, when `brand.json#vertical` is set, the matching `schema-<vertical>.md`) so every `kind` you emit is a label the writer will accept.
|
|
104
104
|
|
|
105
105
|
**Document mode (default).** Build the JSON with this top-level shape:
|
|
106
106
|
|
|
@@ -145,7 +145,7 @@ Other (label fallback) | anchorEdge: null — section-kind closure mis
|
|
|
145
145
|
|
|
146
146
|
`documentEdges` covers party-like links the document itself names: `PARTY` for contract parties, `PARTICIPANT` for meeting/call attendees, `FROM`/`TO`/`CC` for email-header parties, `SPEAKER` for transcript voices. The `memory-ingest` tool definition spells out the exact field shape.
|
|
147
147
|
|
|
148
|
-
**Typed-entity edges target real entities, never `:Section
|
|
148
|
+
**Typed-entity edges target real entities, never `:Section`.** Edge types governed by the typed-edge allowlist — `FOUNDED`, `WORKS_AT`, `INVESTED_IN`, `ADVISES`, `MENTIONS`, `AUTHORED`, `ATTACHED_TO`, `REFERENCES`, `ATTENDED` — connect real entities (`Person`, `Organization`, etc.) per the (source, target) shapes declared in `platform/plugins/memory/mcp/src/lib/typed-edge-schema.ts`. They MUST NOT land on a `:Section` node as either endpoint. The writer rejects loud with the offending `(:Source)-[:EDGE]->(:Target)` triple if a classifier emission violates this.
|
|
149
149
|
|
|
150
150
|
When a section names entities related to the **document anchor** (e.g. a Team section on a company-page document whose anchor is the `:Organization`), set `edge.targetMode = "anchor"` on the related entry. The writer connects the related entity to the anchor instead of the section node:
|
|
151
151
|
|
|
@@ -296,7 +296,7 @@ Extract:
|
|
|
296
296
|
|
|
297
297
|
Process each text-based attachment (PDF, plain text, markdown). Skip images — they have no text to ingest.
|
|
298
298
|
|
|
299
|
-
The `storagePath` you pass to `memory-ingest-extract` is threaded through to the writer automatically (via the extract cache) and persisted on the `:KnowledgeDocument` as `sourcePath`, so the sidebar download resolver can serve the real file later
|
|
299
|
+
The `storagePath` you pass to `memory-ingest-extract` is threaded through to the writer automatically (via the extract cache) and persisted on the `:KnowledgeDocument` as `sourcePath`, so the sidebar download resolver can serve the real file later. You do not pass it to `memory-ingest` — the cache carries it. Web ingests are excepted: `memory-ingest-web`'s temp file is unlinked after extraction, so no `sourcePath` is persisted and the doc stays non-downloadable by design.
|
|
300
300
|
|
|
301
301
|
## Visibility scope
|
|
302
302
|
|
|
@@ -318,7 +318,7 @@ CV / personal-profile content defaults to `admin`. Pricing guides, services, FAQ
|
|
|
318
318
|
6. **Idempotent re-ingest.** `memory-ingest` is safe to re-run with the same `attachmentId`. Prior `:Section` children (any secondary label) and `:Project` standalones are deleted; prior KD-level edges are deleted by **provenance match** — every edge stamped `sourceDocumentId=<this attachmentId>` is dropped, regardless of edge type. The legacy `PARTY` edge type is also matched by name as a belt-and-braces for pre-stamping data. Any KD-level edge that this pipeline writes (classifier-emitted PARTY/PARTICIPANT/FROM/TO/CC/SPEAKER and agent-emitted brief-wired MENTIONS/REFERENCES/etc.) MUST carry the stamp — without it, the edge persists across re-ingests as a duplicate. MERGEd shared entities (Organizations, Persons referenced by other docs) are spared.
|
|
319
319
|
7. **Loud failure, never silent landfill.** Classifier failure aborts the ingest; nothing is written. Edge orphans are surfaced via `orphanCandidates`; the writer never synthesises edges to "fix" them.
|
|
320
320
|
8. **Brief is a deliverable, not a hint.** Step 4 is mandatory whenever the dispatch brief named entities. Returning *"document ingested as a KnowledgeDocument anchored to <X>"* without listing wired/unresolved brief entities is a silent failure — the document becomes an island anchored to one node while the named Persons/Organizations/Tasks stay disconnected, defeating adjacency-ranked `memory-search`. The `[document-ingest] wire-brief-entities` log line is the verification surface; `edges=0` while the brief listed entities is a regression.
|
|
321
|
-
9. **Typed-entity edges never target a `:Section
|
|
321
|
+
9. **Typed-entity edges never target a `:Section`.** Edge types declared in `TYPED_EDGE_ALLOWLIST` (`FOUNDED`, `WORKS_AT`, `INVESTED_IN`, `ADVISES`, `MENTIONS`, `AUTHORED`, `ATTACHED_TO`, `REFERENCES`, `ATTENDED`) must honour the (source, target) label shapes declared there. When the natural target is the document anchor (e.g. founders in a company-page Team section), set `edge.targetMode = "anchor"` on the related entry — the writer routes the edge to the anchor instead of the section. The writer rejects loud with the offending `(:Source)-[:EDGE]->(:Target)` triple on any classifier emission that violates the allowlist.
|
|
322
322
|
|
|
323
323
|
## Output contract
|
|
324
324
|
|
package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/daily-notes.md
CHANGED
|
@@ -16,7 +16,7 @@ Signals are evaluated in that order; the first match wins. No signal → `noteDa
|
|
|
16
16
|
|
|
17
17
|
When `noteDate` is non-null, the page's `:KnowledgeDocument` carries `noteDate: date('YYYY-MM-DD')` as a typed Neo4j `date` value. This is queryable: `MATCH (k:KnowledgeDocument) WHERE k.noteDate >= date('2026-01-01') RETURN k` returns every daily note from this year, without needing a separate `:Day` label.
|
|
18
18
|
|
|
19
|
-
No `:Day` node is created. The brief originally proposed `(:Page)-[:HAS_NOTE]->(:Day {date})`; the Maxy graph encodes the same information as a property on the page itself, mirroring `:Person.birthDate` and `:Event.eventDate`. Date-bearing event semantics live on `:TimelineEvent
|
|
19
|
+
No `:Day` node is created. The brief originally proposed `(:Page)-[:HAS_NOTE]->(:Day {date})`; the Maxy graph encodes the same information as a property on the page itself, mirroring `:Person.birthDate` and `:Event.eventDate`. Date-bearing event semantics live on `:TimelineEvent`, not here — a daily note is a document-at-a-date, not an event-on-a-date.
|
|
20
20
|
|
|
21
21
|
## Folder semantics
|
|
22
22
|
|
|
@@ -23,7 +23,7 @@ This skill is invoked two ways, and they want opposite outputs. Decide which one
|
|
|
23
23
|
- Do not invent requirements or add scope the user did not ask for. If the prompt was already clear, say so in one line and restate it tightly.
|
|
24
24
|
- The restated brief is bound by the three doctrines (see `/doctrine`): precise/concise/plain, no speculation, observability-first. If the user's wording carries a speculative claim or skips an observability step the doctrines require, the restatement corrects it rather than echoing the slip.
|
|
25
25
|
- Reminder — source of knowledge: every factual claim in the restated brief, and in the work you then do from it, traces to one of IDENTITY's four permissible sources (a graph read this session, a tool output returned this session, content the operator pasted into this conversation, or the body of a loaded skill such as platform-architecture). Pretraining recall is not a source. If a fact the brief needs does not trace to one of the four, do not bake it in from memory — read the graph, run the tool, load the skill, or ask the operator first.
|
|
26
|
-
- End the restated brief with the three-tier routing ladder, applied to the restated task in order. **(1)** Delegate to the specialist that owns the deliverable's type via the Agent tool: ownership covers origination, not just the final render, and the brief states the outcome plus the binding constraints, never the lines, anchors, or literal text to insert. **(2)** Only if no installed specialist owns it, load a skill from the admin-usable list with skill-load and do the work from this seat. **(3)** Only if neither fits, author it directly — freestyle inline authoring is the last resort, not a peer of the two routes above. The live specialist roster and admin-usable list are injected each turn by the standing prompt-optimiser directive; the ranked ladder is the classifier.
|
|
26
|
+
- End the restated brief with the three-tier routing ladder, applied to the restated task in order. **(1)** Delegate to the specialist that owns the deliverable's type via the Agent tool: ownership covers origination, not just the final render, and the brief states the outcome plus the binding constraints, never the lines, anchors, or literal text to insert. **(2)** Only if no installed specialist owns it, load a skill from the admin-usable list with skill-load and do the work from this seat. **(3)** Only if neither fits, author it directly — freestyle inline authoring is the last resort, not a peer of the two routes above. The live specialist roster and admin-usable list are injected each turn by the standing prompt-optimiser directive; the ranked ladder is the classifier. The closing line is not the trigger; the classifier is.
|
|
27
27
|
|
|
28
28
|
Then do the work from the restated task. Everything below this section is for the other mode and does not apply — stop reading here.
|
|
29
29
|
|
|
@@ -9,7 +9,7 @@ This plugin is a **frozen vendored copy** of the open-source Slides™ deck fram
|
|
|
9
9
|
| Pinned commit | `578af900d537e8a2bd8bfca11a4d72dc4e8fb588` |
|
|
10
10
|
| Upstream plugin version | `1.0.0` |
|
|
11
11
|
| License | MIT — see [`LICENSE`](LICENSE) (Copyright © 2026 Nils Smed) |
|
|
12
|
-
| Vendored | 2026-06-05
|
|
12
|
+
| Vendored | 2026-06-05 |
|
|
13
13
|
|
|
14
14
|
## What was copied
|
|
15
15
|
|
|
@@ -177,7 +177,7 @@ Audit query: `MATCH ()-[r]->() WHERE r.createdByAgent='substack-import' RETURN D
|
|
|
177
177
|
- **New edge types.** No `:SUBSCRIBED_TO`, `:OPENED`, `:CLICKED`. Subscription is `:MENTIONS`; engagement is properties on that edge.
|
|
178
178
|
- **Per-event engagement detail.** Per-subscriber-per-post events are aggregated to per-edge totals; the individual open/click events are not preserved.
|
|
179
179
|
- **Comments.** Substack export does not include reader comments.
|
|
180
|
-
- **Inbound newsletter ingestion.** A Substack post landing in the operator's Gmail goes through `conversation-archive --source email
|
|
180
|
+
- **Inbound newsletter ingestion.** A Substack post landing in the operator's Gmail goes through `conversation-archive --source email`, not this plugin.
|
|
181
181
|
- **Live API or RSS poll.** Operator-dropped export only.
|
|
182
182
|
- **Single-URL ingestion via `WebFetch`.** A Substack URL passed to `document-ingest` via `memory-ingest-web` will NOT recover the article's verbatim prose for gated / paywalled / subscriber-only posts — `WebFetch` retrieves only the public, server-rendered surface (meta-description, preview blurb, third-person publisher summary). Even for free public posts, JS-rendering on Substack means the WebFetch return is unreliable for verbatim capture. **If the operator hands over a Substack URL, advise:** (a) for the operator's own publication — run the export-archive flow this skill owns (Substack admin → "Export your data" → drop the ZIP); (b) for a third-party publication — paste the article text into chat, or save the page as PDF / `.txt` and attach the file. Then re-dispatch the librarian on the pasted text or attached file. Do not attempt URL-based ingestion as a fallback — the prose will be lost. (Recurring failure surfaced on session `d725df52` 2026-05-27; see `.tasks/465-memory-ingest-body-server-sliced-from-offsets.md` and the WebFetch-limitation note in `document-ingest/SKILL.md`.)
|
|
183
183
|
- **Audio / video enclosures.** Substack podcast / video posts use additional file shapes the v1 plugin does not handle; only images referenced from post-body `<img>` tags are enclosed.
|
|
@@ -46,7 +46,7 @@ Walks users through connecting Telegram bots. Covers bot creation, token configu
|
|
|
46
46
|
|
|
47
47
|
## Setup
|
|
48
48
|
|
|
49
|
-
Connecting a Telegram bot runs through the `telegram:configure` skill (`skills/configure/SKILL.md`) — the brand-aware, PTY-only setup flow. It is the maxy `telegram:configure`; on `-code` brands the upstream `claude-plugins-official/telegram` plugin is not installed, so there is no shadowing
|
|
49
|
+
Connecting a Telegram bot runs through the `telegram:configure` skill (`skills/configure/SKILL.md`) — the brand-aware, PTY-only setup flow. It is the maxy `telegram:configure`; on `-code` brands the upstream `claude-plugins-official/telegram` plugin is not installed, so there is no shadowing. The skill stores the BotFather token under the brand install, registers the webhook on the `/api/telegram/` edge path, sets access policy, and selects the responding agent.
|
|
50
50
|
|
|
51
51
|
## References
|
|
52
52
|
|
|
@@ -43,7 +43,7 @@ operator chooses to ingest it. When a summary is wanted, the caller invokes
|
|
|
43
43
|
url-get from a delegated subagent — the subagent holds the full `text` in its own
|
|
44
44
|
context, summarises there, and returns only the summary to the main agent. This
|
|
45
45
|
keeps both the verbatim text and the summarisation model out of the main
|
|
46
|
-
conversation, and keeps `claude --print` out of url-get entirely
|
|
46
|
+
conversation, and keeps `claude --print` out of url-get entirely.
|
|
47
47
|
|
|
48
48
|
## Why it exists
|
|
49
49
|
|
|
@@ -61,7 +61,7 @@ summarisation, when needed, is the delegated subagent's job, not url-get's.
|
|
|
61
61
|
|
|
62
62
|
- **Server-rendered HTML only.** `url-get` does not execute JavaScript — the same
|
|
63
63
|
coverage limit as a plain fetch. JS-rendered pages are handled by the
|
|
64
|
-
pre-installed browser MCP
|
|
64
|
+
pre-installed browser MCP.
|
|
65
65
|
- **No graph write.** `url-get` writes a reference file and returns cleaned text;
|
|
66
66
|
it never touches Neo4j. Ingestion is `librarian`'s job, reading the reference file.
|
|
67
67
|
- **Verbatim on disk is not size-capped** — the reference file holds the full
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whatsapp",
|
|
3
|
-
"description": "WhatsApp messaging channel — Baileys QR pairing, DM policy enforcement, inbound routing, business hours gating, conversation browsing. Reply-only in DMs (no agent-initiated send
|
|
3
|
+
"description": "WhatsApp messaging channel — Baileys QR pairing, DM policy enforcement, inbound routing, business hours gating, conversation browsing. Reply-only in DMs (no agent-initiated send); observation-only in groups (the agent never responds in a group).",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: whatsapp
|
|
3
|
-
description: WhatsApp messaging channel — Baileys QR pairing, DM policy enforcement, inbound routing, business hours gating, conversation browsing. Reply-only in DMs (no agent-initiated send
|
|
3
|
+
description: WhatsApp messaging channel — Baileys QR pairing, DM policy enforcement, inbound routing, business hours gating, conversation browsing. Reply-only in DMs (no agent-initiated send); observation-only in groups (the agent never responds in a group).
|
|
4
4
|
tools:
|
|
5
5
|
- name: whatsapp-login-start
|
|
6
6
|
publicAllowlist: false
|
|
@@ -57,7 +57,7 @@ Activate when the user asks about WhatsApp — connecting, linking, checking sta
|
|
|
57
57
|
- QR-code-based WhatsApp pairing via Baileys (linked device) — `whatsapp-login-start` returns the QR as a scannable Unicode block printed on the PTY surface (the only operator surface); the operator scans it directly from the terminal output
|
|
58
58
|
- Multi-account support (optional, multiple linked numbers)
|
|
59
59
|
- Inbound message routing — sender-keyed `claude` PTY sessions via the loopback claude-session-manager (admin phone → admin agent, public → resolved public agent); a "typing…" presence is sent on receipt as an ack while the agent turn runs
|
|
60
|
-
- Group observation — group messages are stored, persisted, and browsable, but the agent never responds in a group (observation-only
|
|
60
|
+
- Group observation — group messages are stored, persisted, and browsable, but the agent never responds in a group (observation-only)
|
|
61
61
|
- DM policy enforcement (open, allowlist, disabled)
|
|
62
62
|
- Conversation browsing — list active conversations, read message history, inspect group metadata
|
|
63
63
|
- Inbound voice note transcription (OGG Opus → text via local whisper.cpp)
|
|
@@ -76,17 +76,17 @@ Four tools for reading WhatsApp conversation context. The first two read from th
|
|
|
76
76
|
| `whatsapp-conversation-graph-state` | Persisted `:Message:WhatsAppMessage` rows attached to the `:Conversation` (sessionKey-keyed) plus the resolved `sessionId`, the cypher string, and a divergence header reporting graph-row count vs in-memory count. Use when the question is "what got persisted" — never compose the cypher manually |
|
|
77
77
|
| `whatsapp-group-info` | Group metadata from WhatsApp servers — subject, description, participants with admin flags, creation date. Requires active connection |
|
|
78
78
|
|
|
79
|
-
## Group access — observation-only
|
|
79
|
+
## Group access — observation-only
|
|
80
80
|
|
|
81
81
|
The agent never responds in a group. `checkGroupAccess` ([`inbound/access-control.ts`](../../ui/app/lib/whatsapp/inbound/access-control.ts)) is a structural observe-only deny — it always returns `allowed:false reason:group-observe-only`, so every group inbound ends at the manager's `!accessResult.allowed` return before dispatch. There is no group policy, allowlist, per-group activation, or @mention setting; setting any of those keys is a `.strict()` schema rejection, not a silently-dead key. A belt invariant in the manager's `reply` closure refuses any `@g.us` recipient and logs `op=group-reply-blocked` — that line is the leak signature if a dispatch path ever leaks.
|
|
82
82
|
|
|
83
83
|
Observation is fully preserved: group messages are stored in the ring buffer, recorded as activity, and persisted to the graph at the access-independent capture site, and remain visible via `whatsapp-conversations`, `whatsapp-group-info`, `list-groups`, and the `/whatsapp` reader. This is a Meta-policy constraint (automated messaging from an unofficial linked device is penalised, the same class as the reply-only DM posture); for agent-driven group messaging, Telegram is the recommended channel.
|
|
84
84
|
|
|
85
|
-
## Native channel reply surface
|
|
85
|
+
## Native channel reply surface
|
|
86
86
|
|
|
87
87
|
A native admin channel session runs a per-sender `whatsapp-channel` MCP server (`platform/services/whatsapp-channel/`) exposing two reply-only tools: `reply` (text) and `reply-document` (one or more files delivered as WhatsApp documents). `reply-document` POSTs to the gateway's `POST /wa-channel/reply-document`, which funnels through `sendWhatsAppDocument` — the single send core that owns account-dir path validation, the 100 MB ceiling, the canonical `[whatsapp:outbound] sent document …` log, and per-file reconciliation (`op=reply-document-reconciled` on delivery, `file-delivery-unreconciled` on a miss — no silent failure). A bare `SendUserFile` is also caught and delivered through the same core by a read-only JSONL follower on the session, so an attachment reaches the sender even when the agent skips the explicit tool. Both surfaces are reply-only: a file reply to a sender with no live conversation is refused. See [channels-whatsapp.md](references/channels-whatsapp.md) and `.docs/whatsapp-inbound-lifeline.md`.
|
|
88
88
|
|
|
89
|
-
## Self-chat command lane
|
|
89
|
+
## Self-chat command lane
|
|
90
90
|
|
|
91
91
|
The agent links as a companion device on the operator's own WhatsApp account, so the operator cannot DM the agent from the paired handset — those messages arrive as `fromMe=true` / upsert type `append`. The operator's **self-chat** (message-yourself thread) is the command lane from that handset: the `append` branch in `manager.ts` evaluates a six-clause discriminator (`inbound/self-chat.ts`) — fromMe, self-surface jid (selfJid/selfLid/`selfPhone@s.whatsapp.net`, device-suffix normalised), not an agent-sent echo, timestamp strictly after `lastConnectedAt` (excludes reconnect history backfill; restart-safe backstop for the 2-min echo cache), not a duplicate, non-empty text — and dispatches a match to the **admin** agent with raw text; the reply lands back in the self-chat. Every self-chat `fromMe` message emits one `[whatsapp:self-chat] op=seen` line followed by `op=dispatch` or `op=skip reason=echo|backfill|duplicate|no-text`, so a dropped command is never silent. `fromMe` in any other thread is unchanged (`[Owner reply]` mirror). See [channels-whatsapp.md](references/channels-whatsapp.md) §Self-chat command lane.
|
|
92
92
|
|
|
@@ -6,13 +6,13 @@ WhatsApp connects via Baileys multi-account:
|
|
|
6
6
|
|----------|----------|------------|
|
|
7
7
|
| **baileys** | All WhatsApp accounts | WebSocket via QR code (linked device) |
|
|
8
8
|
|
|
9
|
-
## Reply-only posture
|
|
9
|
+
## Reply-only posture
|
|
10
10
|
|
|
11
11
|
The agent has **no tool to initiate a WhatsApp message to an arbitrary number.** The `whatsapp-send` and `whatsapp-send-document` MCP tools were removed after an agent-initiated cold first-contact send (to a number with no prior conversation) tripped WhatsApp's anti-automation guard and restricted the operator's account. Initiating is intentionally unavailable, not merely gated — a soft approval gate already existed and did not prevent the incident.
|
|
12
12
|
|
|
13
|
-
What the agent **can** still do: reply within an existing chat. On a native admin channel session the whatsapp-channel server (`platform/services/whatsapp-channel/`) exposes two reply tools, both reply-only: `reply` (text) and `reply-document` (one or more files delivered as WhatsApp documents). `reply-document` POSTs to `POST /wa-channel/reply-document` → `sendWhatsAppDocument`, the one send core that owns path validation, the 100 MB ceiling, the canonical log, and per-file reconciliation
|
|
13
|
+
What the agent **can** still do: reply within an existing chat. On a native admin channel session the whatsapp-channel server (`platform/services/whatsapp-channel/`) exposes two reply tools, both reply-only: `reply` (text) and `reply-document` (one or more files delivered as WhatsApp documents). `reply-document` POSTs to `POST /wa-channel/reply-document` → `sendWhatsAppDocument`, the one send core that owns path validation, the 100 MB ceiling, the canonical log, and per-file reconciliation. A bare `SendUserFile` is also caught and delivered through the same core by a read-only JSONL follower on the session, so an attachment reaches the sender even if the agent skips the explicit tool. File delivery is refused for a sender with no live conversation — same reply-only posture as text. (The removed `whatsapp-send-document` route tool and the bridge path `SendUserFile` → `file-delivery-bridge.ts` → `POST /api/whatsapp/send-document` are the legacy webchat/email bridge surface, not the native WhatsApp channel.) Replying within your chats is what WhatsApp permits; starting new chats from a linked device is what it penalises.
|
|
14
14
|
|
|
15
|
-
## Observation-only in groups
|
|
15
|
+
## Observation-only in groups
|
|
16
16
|
|
|
17
17
|
The agent **never responds in a group chat.** Every inbound group message is stored, recorded, and persisted to the graph (observation is fully preserved — `list-groups`, `whatsapp-group-info`, conversations, and the `/whatsapp` reader all still work), but it is never dispatched to a responding session and never produces an outbound message into the group. `checkGroupAccess` is a structural observe-only deny — there is no group policy, allowlist, per-group activation, or @mention setting to turn responses on. A belt invariant in the manager's `reply` closure refuses any `@g.us` recipient (`op=group-reply-blocked`).
|
|
18
18
|
|
|
@@ -118,7 +118,7 @@ Admin phones always bypass DM policy — the `isAdminPhone()` check runs before
|
|
|
118
118
|
|
|
119
119
|
**Schema constraint:** `dmPolicy: "open"` requires `allowFrom` to include `"*"` (enforced by Zod validation).
|
|
120
120
|
|
|
121
|
-
**Key file:** `access-control.ts` — `checkDmAccess()` gates DMs by the policy above; `checkGroupAccess()` is a structural observe-only deny (always `allowed:false reason:group-observe-only
|
|
121
|
+
**Key file:** `access-control.ts` — `checkDmAccess()` gates DMs by the policy above; `checkGroupAccess()` is a structural observe-only deny (always `allowed:false reason:group-observe-only`) — no group policy is consulted.
|
|
122
122
|
|
|
123
123
|
**Config path:** Per-account at `whatsapp.accounts.{accountId}.dmPolicy` in `account.json`. Falls back to `whatsapp.dmPolicy`. Changes are conversational — the admin agent writes to config via `whatsapp-config action: "update-config"`.
|
|
124
124
|
|
|
@@ -150,7 +150,7 @@ The dedupe cache (`agentSentMessages`) has a 2-minute TTL and 500-entry cap, whi
|
|
|
150
150
|
| `src/web/inbound/dedupe.ts` | `trackAgentSentMessage` / `isAgentSentMessage` — echo suppression cache |
|
|
151
151
|
| `src/infra/outbound/cross-agent-echo.ts` | Adapter: agent tool → `mirrorOwnerReplyToSession` |
|
|
152
152
|
|
|
153
|
-
## Self-chat command lane
|
|
153
|
+
## Self-chat command lane
|
|
154
154
|
|
|
155
155
|
The companion-device link means the operator's paired phone cannot DM the agent — a message typed from the paired number arrives as `fromMe=true` with upsert type `append`, which the manager's append branch historically dropped before dispatch. The operator's **self-chat** (the message-yourself thread, `remoteJid` == the account's own jid/lid) is the exception: the agent never writes there first, so it is an unambiguous admin command surface.
|
|
156
156
|
|
|
@@ -70,7 +70,7 @@ The form's `submitMessage` must map the field values back to `update-config` fie
|
|
|
70
70
|
|
|
71
71
|
The agent is **observation-only in groups**: it sees and records every group message but never replies in a group. There is no per-group setting to turn responses on — the restriction is structural, not a toggle. `whatsapp-config action: list-groups` still lists the groups the connected phone is in, for reference.
|
|
72
72
|
|
|
73
|
-
This is a **Meta-policy constraint, not a Maxy limitation.** WhatsApp penalises automated and business-style messaging sent from an unofficial linked device. Replying in groups and sending cold first-contact messages are both anti-automation triggers — they restricted a live operator account, which is why the agent is observe-only in groups and reply-only in DMs
|
|
73
|
+
This is a **Meta-policy constraint, not a Maxy limitation.** WhatsApp penalises automated and business-style messaging sent from an unofficial linked device. Replying in groups and sending cold first-contact messages are both anti-automation triggers — they restricted a live operator account, which is why the agent is observe-only in groups and reply-only in DMs.
|
|
74
74
|
|
|
75
75
|
If the user wants an agent that actively messages in groups or initiates conversations, recommend **Telegram** instead. Telegram has no equivalent linked-device automation penalty, so the agent can send and respond freely there. Point them to the Telegram setup path at `platform/plugins/telegram/references/setup-guide.md`.
|
|
76
76
|
|
|
@@ -124,7 +124,7 @@ Each step declares an `onFailure` policy:
|
|
|
124
124
|
| `skipped` | Step failed but `onFailure: "skip"` kept execution going |
|
|
125
125
|
| `failed` | Step failed and `onFailure: "abort"` stopped execution |
|
|
126
126
|
|
|
127
|
-
(Historic `degraded` step results from
|
|
127
|
+
(Historic `degraded` step results from an earlier runner version remain in the graph; the runner no longer produces them.)
|
|
128
128
|
|
|
129
129
|
## Execution History
|
|
130
130
|
|
|
@@ -21,7 +21,7 @@ The admin agent delegates to `specialists:database-operator` when the operator d
|
|
|
21
21
|
|
|
22
22
|
## What this plugin is not
|
|
23
23
|
|
|
24
|
-
- **Not a new graph writer.** Every write routes through the existing `memory-ingest` surface — the document path keyed on `attachmentId` (tweet stream) writes `:KnowledgeDocument`, or the conversation-archive path keyed on `conversationIdentity` (DMs) writes `:ConversationArchive
|
|
24
|
+
- **Not a new graph writer.** Every write routes through the existing `memory-ingest` surface — the document path keyed on `attachmentId` (tweet stream) writes `:KnowledgeDocument`, or the conversation-archive path keyed on `conversationIdentity` (DMs) writes `:ConversationArchive`. Both attach `:Section` children. No bespoke `:Post`, `:DirectMessage`, `:DMThread`, `:Attachment`, or `:Thread` label is introduced; per-thread KD granularity is explicitly rejected.
|
|
25
25
|
- **Not a live X API client.** Archive-only — the X API is rate-limited and paid.
|
|
26
26
|
- **Not a retweet importer.** Retweets-without-comment are not persisted reliably in X archives; the skill drops them.
|
|
27
27
|
|
|
@@ -29,5 +29,5 @@ The admin agent delegates to `specialists:database-operator` when the operator d
|
|
|
29
29
|
|
|
30
30
|
- **memory** — the underlying graph-write surface used by the skill (`mcp__plugin_memory_memory__memory-write` etc.). The skill passes `source='x'` / `source='x-dm'` and `createdByAgent='x-import'` for provenance on every write.
|
|
31
31
|
- **document-ingest** (memory skill) — owns the tweet-stream KnowledgeDocument writes. `:Section` classification is its responsibility, not this plugin's.
|
|
32
|
-
- **conversation-archive** (memory skill) — owns the DM transcript writes (`:ConversationArchive` keyed on `conversationIdentity`
|
|
32
|
+
- **conversation-archive** (memory skill) — owns the DM transcript writes (`:ConversationArchive` keyed on `conversationIdentity`). The `x-dm` enum entry on `CONVERSATION_SOURCES` and the sibling normaliser in `platform/plugins/memory/mcp/src/lib/conversation-normalisers/x-dm.ts` are the only new code outside this plugin.
|
|
33
33
|
- **database-operator specialist** — owns execution. See [platform/templates/specialists/agents/database-operator.md](../../templates/specialists/agents/database-operator.md) external-archive ingestion clause.
|
|
@@ -118,6 +118,6 @@ Every `.js` shim read by this skill is gated through the wrapper regex documente
|
|
|
118
118
|
|
|
119
119
|
- **Per-thread KDs.** One archive = one tweet-stream KD. Thread boundaries are section boundaries the classifier owns, not graph objects.
|
|
120
120
|
- **Bespoke labels.** No `:Post`, `:Tweet`, `:DirectMessage`, `:DMThread`, `:Attachment`. Everything maps to the existing `:KnowledgeDocument` + `:ConversationArchive` + `:ImageObject` shapes.
|
|
121
|
-
- **Tweet-level typed edges.** `:REPLIES_TO` / `:QUOTES` as graph edges off individual tweets are rejected; document-level `REPLIED_TO` / `QUOTED` from the KD to each `:Person` covers the same traversals at the right granularity. The
|
|
121
|
+
- **Tweet-level typed edges.** `:REPLIES_TO` / `:QUOTES` as graph edges off individual tweets are rejected; document-level `REPLIED_TO` / `QUOTED` from the KD to each `:Person` covers the same traversals at the right granularity. The typed-edge pass enumerates the new KD and writes any further `COMMITTED_TO` / `MENTIONS` / `REFERENCES` edges from in-body references.
|
|
122
122
|
- **Retweets-without-comment.** Not persisted reliably in X archives; dropped at parse.
|
|
123
123
|
- **Live X API.** Archive-only.
|