@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
package/package.json
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export declare const SAME_SUBJECT_COSINE = 0.85;
|
|
2
|
-
export declare const INJECTION_RULE_CAP = 12;
|
|
3
2
|
export declare const ACTIVE_CANDIDATE_FLOOR = 0.4;
|
|
4
|
-
export declare const ACTIVE_CANDIDATE_LIMIT = 30;
|
|
5
3
|
export interface RuleCandidate {
|
|
6
4
|
preferenceId: string;
|
|
7
5
|
category: string;
|
|
@@ -19,9 +17,7 @@ export interface ActiveRule {
|
|
|
19
17
|
confidence: number;
|
|
20
18
|
observedAt: string;
|
|
21
19
|
}
|
|
22
|
-
export declare function
|
|
23
|
-
export declare function deconflictBySubject(records: RuleCandidate[], cosineThreshold?: number): ActiveRule[];
|
|
24
|
-
export declare function formatStandingRulesBlock(rules: ActiveRule[], cap?: number): string;
|
|
20
|
+
export declare function formatStandingRulesBlock(rules: ActiveRule[]): string;
|
|
25
21
|
interface MinimalSession {
|
|
26
22
|
run: (q: string, p: Record<string, unknown>) => Promise<{
|
|
27
23
|
records: Array<{
|
|
@@ -43,7 +39,7 @@ export interface StandingRulesInjection {
|
|
|
43
39
|
}
|
|
44
40
|
export declare function resolveOwnerUserId(session: MinimalSession, accountId: string): Promise<string | null>;
|
|
45
41
|
export declare function queryActiveRuleCandidates(session: MinimalSession, accountId: string): Promise<RuleCandidate[]>;
|
|
46
|
-
export declare function resolveActiveRules(session: MinimalSession, accountId: string
|
|
47
|
-
export declare function activeRuleSet(session: MinimalSession, accountId: string
|
|
42
|
+
export declare function resolveActiveRules(session: MinimalSession, accountId: string): Promise<ActiveRuleResolution>;
|
|
43
|
+
export declare function activeRuleSet(session: MinimalSession, accountId: string): Promise<ActiveRule[]>;
|
|
48
44
|
export {};
|
|
49
45
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAIpE;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,CACH,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACvB,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAC/D;AAMD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,UAAU,GAAG,aAAa,CAAC;AACpE,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAE/D,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAID,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,eAAe,CAAC;CACzB;AAOD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAaxB;AAgED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,EAAE,CAAC,CAE1B;AAKD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,oBAAoB,CAAC,CAI/B;AAID,wBAAsB,aAAa,CACjC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,EAAE,CAAC,CAEvB"}
|
|
@@ -5,56 +5,25 @@
|
|
|
5
5
|
// free of neo4j-driver types so both an MCP process and the UI can import the
|
|
6
6
|
// built dist.
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.
|
|
9
|
-
exports.cosine = cosine;
|
|
10
|
-
exports.deconflictBySubject = deconflictBySubject;
|
|
8
|
+
exports.ACTIVE_CANDIDATE_FLOOR = exports.SAME_SUBJECT_COSINE = void 0;
|
|
11
9
|
exports.formatStandingRulesBlock = formatStandingRulesBlock;
|
|
12
10
|
exports.resolveOwnerUserId = resolveOwnerUserId;
|
|
13
11
|
exports.queryActiveRuleCandidates = queryActiveRuleCandidates;
|
|
14
12
|
exports.resolveActiveRules = resolveActiveRules;
|
|
15
13
|
exports.activeRuleSet = activeRuleSet;
|
|
14
|
+
// Write-time only: profile-update routes a same-subject write (a neighbour at
|
|
15
|
+
// or above this raw-cosine threshold) into the existing node instead of minting
|
|
16
|
+
// a duplicate. Injection no longer dedups — it surfaces every stored active
|
|
17
|
+
// preference verbatim (Task 1951) — so this constant has no read-side use.
|
|
16
18
|
exports.SAME_SUBJECT_COSINE = 0.85;
|
|
17
|
-
exports.INJECTION_RULE_CAP = 12;
|
|
18
19
|
exports.ACTIVE_CANDIDATE_FLOOR = 0.4;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let dot = 0;
|
|
24
|
-
let na = 0;
|
|
25
|
-
let nb = 0;
|
|
26
|
-
for (let i = 0; i < a.length; i++) {
|
|
27
|
-
dot += a[i] * b[i];
|
|
28
|
-
na += a[i] * a[i];
|
|
29
|
-
nb += b[i] * b[i];
|
|
30
|
-
}
|
|
31
|
-
if (na === 0 || nb === 0)
|
|
32
|
-
return 0;
|
|
33
|
-
return dot / (Math.sqrt(na) * Math.sqrt(nb));
|
|
34
|
-
}
|
|
35
|
-
// records MUST be confidence-desc on entry. Greedy: the first record of a
|
|
36
|
-
// cluster is the winner; any later record within cosineThreshold of an
|
|
37
|
-
// already-kept winner is a rephrasing and is dropped. A record with no usable
|
|
38
|
-
// embedding is never folded — it is kept as its own subject (we cannot prove it
|
|
39
|
-
// is a duplicate).
|
|
40
|
-
function deconflictBySubject(records, cosineThreshold = exports.SAME_SUBJECT_COSINE) {
|
|
41
|
-
const winners = [];
|
|
42
|
-
for (const r of records) {
|
|
43
|
-
const emb = r.embedding;
|
|
44
|
-
const isDup = Array.isArray(emb) &&
|
|
45
|
-
emb.length > 0 &&
|
|
46
|
-
winners.some((w) => Array.isArray(w.embedding) &&
|
|
47
|
-
w.embedding.length > 0 &&
|
|
48
|
-
cosine(emb, w.embedding) >= cosineThreshold);
|
|
49
|
-
if (!isDup)
|
|
50
|
-
winners.push(r);
|
|
51
|
-
}
|
|
52
|
-
return winners.map(({ embedding: _embedding, ...rest }) => rest);
|
|
53
|
-
}
|
|
54
|
-
function formatStandingRulesBlock(rules, cap = exports.INJECTION_RULE_CAP) {
|
|
20
|
+
// Render every rule verbatim. Injection has no cap (Task 1951): concision is
|
|
21
|
+
// enforced at write time by the conversational-memory skill so each stored
|
|
22
|
+
// value is already short, not by truncating the block here.
|
|
23
|
+
function formatStandingRulesBlock(rules) {
|
|
55
24
|
if (rules.length === 0)
|
|
56
25
|
return "";
|
|
57
|
-
const lines = rules.
|
|
26
|
+
const lines = rules.map((r) => `- ${r.value}`);
|
|
58
27
|
return `## Standing rules\n${lines.join("\n")}`;
|
|
59
28
|
}
|
|
60
29
|
// The account's owner identity (Task 1461). One `AdminUser {role:'owner'}` is
|
|
@@ -80,11 +49,8 @@ async function resolveOwnerUserId(session, accountId) {
|
|
|
80
49
|
// most-recent wins de-confliction. The trashed predicate mirrors graph-trash's
|
|
81
50
|
// `notTrashed()` exactly (label + deletedAt) so this lib stays dependency-free.
|
|
82
51
|
async function queryOwnerCandidates(session, accountId, ownerUserId) {
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// rejects it ("not a valid value. Must be a non-negative integer"). This lib
|
|
86
|
-
// is dependency-free (no neo4j-driver), so it cannot call `neo4j.int()`; the
|
|
87
|
-
// constant is a hardcoded integer, so inlining it is safe and gate-clean.
|
|
52
|
+
// No LIMIT — every owner admin preference above the floor is a candidate
|
|
53
|
+
// (Task 1951). The result is bounded by how many the owner has, not by a cap.
|
|
88
54
|
const result = await session.run(`
|
|
89
55
|
MATCH (up:UserProfile {accountId: $accountId, userId: $ownerUserId})-[:HAS_PREFERENCE]->(pref:Preference {scope: 'admin'})
|
|
90
56
|
WHERE NOT pref:Trashed AND pref.deletedAt IS NULL
|
|
@@ -94,7 +60,6 @@ async function queryOwnerCandidates(session, accountId, ownerUserId) {
|
|
|
94
60
|
pref.key AS key, pref.value AS value, pref.confidence AS confidence,
|
|
95
61
|
pref.observedAt AS observedAt, pref.embedding AS embedding
|
|
96
62
|
ORDER BY pref.confidence DESC, pref.observedAt DESC
|
|
97
|
-
LIMIT ${exports.ACTIVE_CANDIDATE_LIMIT}
|
|
98
63
|
`, { accountId, ownerUserId, floor: exports.ACTIVE_CANDIDATE_FLOOR });
|
|
99
64
|
return result.records.map((r) => ({
|
|
100
65
|
preferenceId: r.get("preferenceId"),
|
|
@@ -128,14 +93,17 @@ async function resolveCandidates(session, accountId) {
|
|
|
128
93
|
async function queryActiveRuleCandidates(session, accountId) {
|
|
129
94
|
return (await resolveCandidates(session, accountId)).candidates;
|
|
130
95
|
}
|
|
131
|
-
// The account's active
|
|
132
|
-
|
|
96
|
+
// The account's active rule-set plus its provenance. Every owner candidate is
|
|
97
|
+
// returned verbatim, in `confidence DESC, observedAt DESC` order — no cap, no
|
|
98
|
+
// de-confliction (Task 1951). Only the read-facing `embedding` is dropped.
|
|
99
|
+
async function resolveActiveRules(session, accountId) {
|
|
133
100
|
const { candidates, ownerUserId, source } = await resolveCandidates(session, accountId);
|
|
134
|
-
|
|
101
|
+
const rules = candidates.map(({ embedding: _embedding, ...rest }) => rest);
|
|
102
|
+
return { rules, ownerUserId, source };
|
|
135
103
|
}
|
|
136
104
|
// The rule array alone — the unchanged call boundary for profile-read and the
|
|
137
105
|
// ui/server injection wrapper.
|
|
138
|
-
async function activeRuleSet(session, accountId
|
|
139
|
-
return (await resolveActiveRules(session, accountId
|
|
106
|
+
async function activeRuleSet(session, accountId) {
|
|
107
|
+
return (await resolveActiveRules(session, accountId)).rules;
|
|
140
108
|
}
|
|
141
109
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,uEAAuE;AACvE,sEAAsE;AACtE,gFAAgF;AAChF,8EAA8E;AAC9E,cAAc;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,76 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import {
|
|
3
|
-
cosine,
|
|
4
|
-
deconflictBySubject,
|
|
5
3
|
formatStandingRulesBlock,
|
|
6
4
|
resolveActiveRules,
|
|
7
5
|
activeRuleSet,
|
|
8
6
|
queryActiveRuleCandidates,
|
|
9
|
-
INJECTION_RULE_CAP,
|
|
10
7
|
type RuleCandidate,
|
|
11
8
|
} from "./index.js";
|
|
12
9
|
|
|
13
|
-
const vec = (n: number, fill: number): number[] => Array(n).fill(fill);
|
|
14
|
-
|
|
15
|
-
function cand(over: Partial<RuleCandidate>): RuleCandidate {
|
|
16
|
-
return {
|
|
17
|
-
preferenceId: "p",
|
|
18
|
-
category: "communication",
|
|
19
|
-
key: "k",
|
|
20
|
-
value: "v",
|
|
21
|
-
confidence: 0.5,
|
|
22
|
-
observedAt: "2026-07-01T00:00:00Z",
|
|
23
|
-
embedding: vec(4, 1),
|
|
24
|
-
...over,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe("cosine", () => {
|
|
29
|
-
it("is 1 for identical vectors and 0 for an empty one", () => {
|
|
30
|
-
expect(cosine([1, 0, 0], [1, 0, 0])).toBeCloseTo(1, 6);
|
|
31
|
-
expect(cosine([], [1, 2, 3])).toBe(0);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("deconflictBySubject", () => {
|
|
36
|
-
it("folds a near-duplicate into the higher-confidence winner", () => {
|
|
37
|
-
const a = cand({
|
|
38
|
-
preferenceId: "a",
|
|
39
|
-
key: "header_rule",
|
|
40
|
-
value: "use header A",
|
|
41
|
-
confidence: 0.8,
|
|
42
|
-
embedding: [1, 0, 0, 0],
|
|
43
|
-
});
|
|
44
|
-
const b = cand({
|
|
45
|
-
preferenceId: "b",
|
|
46
|
-
key: "doc_header",
|
|
47
|
-
value: "use header A restated",
|
|
48
|
-
confidence: 0.5,
|
|
49
|
-
embedding: [0.99, 0.01, 0, 0],
|
|
50
|
-
});
|
|
51
|
-
const out = deconflictBySubject([a, b], 0.85);
|
|
52
|
-
expect(out.map((r) => r.preferenceId)).toEqual(["a"]);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("keeps genuinely different subjects", () => {
|
|
56
|
-
const a = cand({ preferenceId: "a", embedding: [1, 0, 0, 0] });
|
|
57
|
-
const b = cand({ preferenceId: "b", embedding: [0, 1, 0, 0] });
|
|
58
|
-
expect(deconflictBySubject([a, b], 0.85).map((r) => r.preferenceId)).toEqual([
|
|
59
|
-
"a",
|
|
60
|
-
"b",
|
|
61
|
-
]);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("never folds a candidate with no embedding", () => {
|
|
65
|
-
const a = cand({ preferenceId: "a", embedding: [1, 0, 0, 0] });
|
|
66
|
-
const b = cand({ preferenceId: "b", embedding: null });
|
|
67
|
-
expect(deconflictBySubject([a, b], 0.85).map((r) => r.preferenceId)).toEqual([
|
|
68
|
-
"a",
|
|
69
|
-
"b",
|
|
70
|
-
]);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
10
|
// A fake Neo4j session that dispatches on query text: the owner-resolution
|
|
75
11
|
// query (matches `role: 'owner'`) returns a configurable owner userId or none;
|
|
76
12
|
// the candidate query (matches HAS_PREFERENCE) returns configurable pref rows
|
|
@@ -115,7 +51,7 @@ const prefRow = (over: Partial<RuleCandidate>): Record<string, unknown> => ({
|
|
|
115
51
|
...over,
|
|
116
52
|
});
|
|
117
53
|
|
|
118
|
-
describe("resolveActiveRules — owner-scoped
|
|
54
|
+
describe("resolveActiveRules — owner-scoped, uncapped, verbatim", () => {
|
|
119
55
|
it("returns the owner's prefs with source=owner when an owner exists", async () => {
|
|
120
56
|
const s = fakeSession({
|
|
121
57
|
ownerId: "owner-35fed05e",
|
|
@@ -132,6 +68,36 @@ describe("resolveActiveRules — owner-scoped selection", () => {
|
|
|
132
68
|
expect(s.candidateRuns).toBe(1);
|
|
133
69
|
});
|
|
134
70
|
|
|
71
|
+
it("returns MORE than 12 candidates with no cap and no truncation", async () => {
|
|
72
|
+
const rows = Array.from({ length: 20 }, (_, i) =>
|
|
73
|
+
prefRow({
|
|
74
|
+
preferenceId: `p${i}`,
|
|
75
|
+
value: `rule ${i}`,
|
|
76
|
+
confidence: 1 - i * 0.01,
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
const s = fakeSession({ ownerId: "owner-x", prefRows: rows });
|
|
80
|
+
const res = await resolveActiveRules(s, "acct-1");
|
|
81
|
+
expect(res.rules.length).toBe(20);
|
|
82
|
+
expect(res.rules.map((r) => r.preferenceId)).toEqual(
|
|
83
|
+
rows.map((r) => r.preferenceId),
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("preserves the candidate order verbatim (no reordering, no dedup)", async () => {
|
|
88
|
+
// Two rows that a cosine dedup would once have folded (identical embeddings)
|
|
89
|
+
// are BOTH returned now — injection never dedups.
|
|
90
|
+
const s = fakeSession({
|
|
91
|
+
ownerId: "owner-1",
|
|
92
|
+
prefRows: [
|
|
93
|
+
prefRow({ preferenceId: "a", value: "use header A", embedding: [1, 0, 0, 0] }),
|
|
94
|
+
prefRow({ preferenceId: "b", value: "use header A restated", embedding: [1, 0, 0, 0] }),
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
const res = await resolveActiveRules(s, "acct-1");
|
|
98
|
+
expect(res.rules.map((r) => r.preferenceId)).toEqual(["a", "b"]);
|
|
99
|
+
});
|
|
100
|
+
|
|
135
101
|
it("returns empty with source=no-owner and never runs the candidate query", async () => {
|
|
136
102
|
const s = fakeSession({ ownerId: null });
|
|
137
103
|
const res = await resolveActiveRules(s, "acct-1");
|
|
@@ -176,10 +142,10 @@ describe("resolveActiveRules — owner-scoped selection", () => {
|
|
|
176
142
|
|
|
177
143
|
describe("formatStandingRulesBlock", () => {
|
|
178
144
|
it("returns empty string for no rules", () => {
|
|
179
|
-
expect(formatStandingRulesBlock([]
|
|
145
|
+
expect(formatStandingRulesBlock([])).toBe("");
|
|
180
146
|
});
|
|
181
147
|
|
|
182
|
-
it("
|
|
148
|
+
it("renders every rule, with no cap", () => {
|
|
183
149
|
const rules = Array.from({ length: 20 }, (_, i) => ({
|
|
184
150
|
preferenceId: `p${i}`,
|
|
185
151
|
category: "communication",
|
|
@@ -188,8 +154,8 @@ describe("formatStandingRulesBlock", () => {
|
|
|
188
154
|
confidence: 1 - i * 0.01,
|
|
189
155
|
observedAt: "2026-07-01T00:00:00Z",
|
|
190
156
|
}));
|
|
191
|
-
const block = formatStandingRulesBlock(rules
|
|
157
|
+
const block = formatStandingRulesBlock(rules);
|
|
192
158
|
expect(block).toContain("## Standing rules");
|
|
193
|
-
expect((block.match(/^- /gm) ?? []).length).toBe(
|
|
159
|
+
expect((block.match(/^- /gm) ?? []).length).toBe(20);
|
|
194
160
|
});
|
|
195
161
|
});
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
// free of neo4j-driver types so both an MCP process and the UI can import the
|
|
5
5
|
// built dist.
|
|
6
6
|
|
|
7
|
+
// Write-time only: profile-update routes a same-subject write (a neighbour at
|
|
8
|
+
// or above this raw-cosine threshold) into the existing node instead of minting
|
|
9
|
+
// a duplicate. Injection no longer dedups — it surfaces every stored active
|
|
10
|
+
// preference verbatim (Task 1951) — so this constant has no read-side use.
|
|
7
11
|
export const SAME_SUBJECT_COSINE = 0.85;
|
|
8
|
-
export const INJECTION_RULE_CAP = 12;
|
|
9
12
|
export const ACTIVE_CANDIDATE_FLOOR = 0.4;
|
|
10
|
-
export const ACTIVE_CANDIDATE_LIMIT = 30;
|
|
11
13
|
|
|
12
14
|
export interface RuleCandidate {
|
|
13
15
|
preferenceId: string;
|
|
@@ -28,52 +30,12 @@ export interface ActiveRule {
|
|
|
28
30
|
observedAt: string;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let nb = 0;
|
|
36
|
-
for (let i = 0; i < a.length; i++) {
|
|
37
|
-
dot += a[i] * b[i];
|
|
38
|
-
na += a[i] * a[i];
|
|
39
|
-
nb += b[i] * b[i];
|
|
40
|
-
}
|
|
41
|
-
if (na === 0 || nb === 0) return 0;
|
|
42
|
-
return dot / (Math.sqrt(na) * Math.sqrt(nb));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// records MUST be confidence-desc on entry. Greedy: the first record of a
|
|
46
|
-
// cluster is the winner; any later record within cosineThreshold of an
|
|
47
|
-
// already-kept winner is a rephrasing and is dropped. A record with no usable
|
|
48
|
-
// embedding is never folded — it is kept as its own subject (we cannot prove it
|
|
49
|
-
// is a duplicate).
|
|
50
|
-
export function deconflictBySubject(
|
|
51
|
-
records: RuleCandidate[],
|
|
52
|
-
cosineThreshold: number = SAME_SUBJECT_COSINE,
|
|
53
|
-
): ActiveRule[] {
|
|
54
|
-
const winners: RuleCandidate[] = [];
|
|
55
|
-
for (const r of records) {
|
|
56
|
-
const emb = r.embedding;
|
|
57
|
-
const isDup =
|
|
58
|
-
Array.isArray(emb) &&
|
|
59
|
-
emb.length > 0 &&
|
|
60
|
-
winners.some(
|
|
61
|
-
(w) =>
|
|
62
|
-
Array.isArray(w.embedding) &&
|
|
63
|
-
w.embedding.length > 0 &&
|
|
64
|
-
cosine(emb, w.embedding) >= cosineThreshold,
|
|
65
|
-
);
|
|
66
|
-
if (!isDup) winners.push(r);
|
|
67
|
-
}
|
|
68
|
-
return winners.map(({ embedding: _embedding, ...rest }) => rest);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function formatStandingRulesBlock(
|
|
72
|
-
rules: ActiveRule[],
|
|
73
|
-
cap: number = INJECTION_RULE_CAP,
|
|
74
|
-
): string {
|
|
33
|
+
// Render every rule verbatim. Injection has no cap (Task 1951): concision is
|
|
34
|
+
// enforced at write time by the conversational-memory skill so each stored
|
|
35
|
+
// value is already short, not by truncating the block here.
|
|
36
|
+
export function formatStandingRulesBlock(rules: ActiveRule[]): string {
|
|
75
37
|
if (rules.length === 0) return "";
|
|
76
|
-
const lines = rules.
|
|
38
|
+
const lines = rules.map((r) => `- ${r.value}`);
|
|
77
39
|
return `## Standing rules\n${lines.join("\n")}`;
|
|
78
40
|
}
|
|
79
41
|
|
|
@@ -139,11 +101,8 @@ async function queryOwnerCandidates(
|
|
|
139
101
|
accountId: string,
|
|
140
102
|
ownerUserId: string,
|
|
141
103
|
): Promise<RuleCandidate[]> {
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
// rejects it ("not a valid value. Must be a non-negative integer"). This lib
|
|
145
|
-
// is dependency-free (no neo4j-driver), so it cannot call `neo4j.int()`; the
|
|
146
|
-
// constant is a hardcoded integer, so inlining it is safe and gate-clean.
|
|
104
|
+
// No LIMIT — every owner admin preference above the floor is a candidate
|
|
105
|
+
// (Task 1951). The result is bounded by how many the owner has, not by a cap.
|
|
147
106
|
const result = await session.run(
|
|
148
107
|
`
|
|
149
108
|
MATCH (up:UserProfile {accountId: $accountId, userId: $ownerUserId})-[:HAS_PREFERENCE]->(pref:Preference {scope: 'admin'})
|
|
@@ -154,7 +113,6 @@ async function queryOwnerCandidates(
|
|
|
154
113
|
pref.key AS key, pref.value AS value, pref.confidence AS confidence,
|
|
155
114
|
pref.observedAt AS observedAt, pref.embedding AS embedding
|
|
156
115
|
ORDER BY pref.confidence DESC, pref.observedAt DESC
|
|
157
|
-
LIMIT ${ACTIVE_CANDIDATE_LIMIT}
|
|
158
116
|
`,
|
|
159
117
|
{ accountId, ownerUserId, floor: ACTIVE_CANDIDATE_FLOOR },
|
|
160
118
|
);
|
|
@@ -201,14 +159,16 @@ export async function queryActiveRuleCandidates(
|
|
|
201
159
|
return (await resolveCandidates(session, accountId)).candidates;
|
|
202
160
|
}
|
|
203
161
|
|
|
204
|
-
// The account's active
|
|
162
|
+
// The account's active rule-set plus its provenance. Every owner candidate is
|
|
163
|
+
// returned verbatim, in `confidence DESC, observedAt DESC` order — no cap, no
|
|
164
|
+
// de-confliction (Task 1951). Only the read-facing `embedding` is dropped.
|
|
205
165
|
export async function resolveActiveRules(
|
|
206
166
|
session: MinimalSession,
|
|
207
167
|
accountId: string,
|
|
208
|
-
cap: number = INJECTION_RULE_CAP,
|
|
209
168
|
): Promise<ActiveRuleResolution> {
|
|
210
169
|
const { candidates, ownerUserId, source } = await resolveCandidates(session, accountId);
|
|
211
|
-
|
|
170
|
+
const rules = candidates.map(({ embedding: _embedding, ...rest }) => rest);
|
|
171
|
+
return { rules, ownerUserId, source };
|
|
212
172
|
}
|
|
213
173
|
|
|
214
174
|
// The rule array alone — the unchanged call boundary for profile-read and the
|
|
@@ -216,7 +176,6 @@ export async function resolveActiveRules(
|
|
|
216
176
|
export async function activeRuleSet(
|
|
217
177
|
session: MinimalSession,
|
|
218
178
|
accountId: string,
|
|
219
|
-
cap: number = INJECTION_RULE_CAP,
|
|
220
179
|
): Promise<ActiveRule[]> {
|
|
221
|
-
return (await resolveActiveRules(session, accountId
|
|
180
|
+
return (await resolveActiveRules(session, accountId)).rules;
|
|
222
181
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:de872279f0c40b8dee0a8ef51dba6534e2452d6034ca2765d6ab912447bc95b3
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -1905,6 +1905,22 @@ A site capture becomes one of these too: an enquiry becomes a new contact routed
|
|
|
1905
1905
|
|
|
1906
1906
|
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.
|
|
1907
1907
|
|
|
1908
|
+
## Automatic email→graph sweep (opt-in)
|
|
1909
|
+
|
|
1910
|
+
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 Maxy already holds, saves the genuinely new ones, and then sends you a short report of what it read and saved.
|
|
1911
|
+
|
|
1912
|
+
Turning it on needs two things, and Maxy tells you which is missing if either is absent:
|
|
1913
|
+
|
|
1914
|
+
- **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 Maxy says so instead of switching anything on.
|
|
1915
|
+
- **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, Maxy tells you that rather than falling back to a silent run.
|
|
1916
|
+
|
|
1917
|
+
Two things to know before you switch it on:
|
|
1918
|
+
|
|
1919
|
+
- **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.
|
|
1920
|
+
- **It is one-account, deliberate opt-in.** Maxy 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.
|
|
1921
|
+
|
|
1922
|
+
Turn it off with the same skill: ask to stop the automatic email sweep and Maxy cancels the schedule, so no further runs fire.
|
|
1923
|
+
|
|
1908
1924
|
---
|
|
1909
1925
|
# Slides
|
|
1910
1926
|
Source: https://docs.getmaxy.com/slides.md
|
|
@@ -2994,6 +3010,35 @@ either is a regression.
|
|
|
2994
3010
|
|
|
2995
3011
|
**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.
|
|
2996
3012
|
|
|
3013
|
+
### Routines
|
|
3014
|
+
|
|
3015
|
+
The Routines surface (`/routines`) is the admin view of the account's
|
|
3016
|
+
automation `:Event` nodes — the device's scheduled agent turns and tool
|
|
3017
|
+
dispatches, which the Calendar surface deliberately excludes (`calendar.ts`
|
|
3018
|
+
unions only appointment events, `actionPlugin IS NULL`). It renders one card per
|
|
3019
|
+
routine (title, human-readable schedule, next run, channel/destination or tool,
|
|
3020
|
+
status); a card opens a detail modal modeled on the calendar `EventDetailModal`.
|
|
3021
|
+
The modal edits the prose instruction (`agentPrompt`, agent-dispatch routines
|
|
3022
|
+
only) and the schedule via a friendly builder (frequency daily/weekly/monthly,
|
|
3023
|
+
day-of-week, day-of-month, time) that falls back to a raw-cron field for patterns
|
|
3024
|
+
it cannot represent.
|
|
3025
|
+
|
|
3026
|
+
The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.ts`
|
|
3027
|
+
(a foreign-account or non-automation `eventId` 404s):
|
|
3028
|
+
|
|
3029
|
+
| Route | Behaviour |
|
|
3030
|
+
|-------|-----------|
|
|
3031
|
+
| `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. |
|
|
3032
|
+
| `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
|
|
3033
|
+
| `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`). |
|
|
3034
|
+
|
|
3035
|
+
Observability rides `[admin:routines]`: `op=list accountId=<8> count=<n>`,
|
|
3036
|
+
`op=get id=<8> found=<bool>`, and `op=patch id=<8> fields=<…> outcome=ok|rejected
|
|
3037
|
+
reason=<invalid-cron|not-found> nextRun=<…> cron=<serialized>`. A `count=0` while
|
|
3038
|
+
routines exist for the account is the account-scope-miss signature; the logged
|
|
3039
|
+
`cron=` on `outcome=ok` confirms the persisted expression without opening the
|
|
3040
|
+
graph.
|
|
3041
|
+
|
|
2997
3042
|
### Graph
|
|
2998
3043
|
|
|
2999
3044
|
| Mount | Purpose |
|
|
@@ -3302,12 +3347,13 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
3302
3347
|
> owns all navigation; the full-width top header bar, the burger flyout, and the
|
|
3303
3348
|
> "Powered by Claude Code" footer strip are retired. After the redesign:
|
|
3304
3349
|
>
|
|
3305
|
-
> - **Nav rows** are Dashboard, Chat, Tasks, Calendar,
|
|
3306
|
-
>
|
|
3307
|
-
>
|
|
3308
|
-
>
|
|
3309
|
-
>
|
|
3310
|
-
>
|
|
3350
|
+
> - **Nav rows** are Dashboard, Chat, Tasks, Calendar, Routines (the automation
|
|
3351
|
+
> `:Event` nodes at `/routines`, `Zap` icon), Data, the brand container
|
|
3352
|
+
> row (Projects / Properties / Jobs, `Folder` icon, opening the graph filtered
|
|
3353
|
+
> to the brand's primary container), Graph (the unfiltered whole graph at
|
|
3354
|
+
> `/graph`, waypoints icon — active on plain `/graph`; the container row is
|
|
3355
|
+
> active only when a `label=` filter is present), Activity, and Browser. The
|
|
3356
|
+
> active row carries an accent left indicator. The older People / Agents / Artefacts entries in the
|
|
3311
3357
|
> table below are pre-redesign.
|
|
3312
3358
|
> - **Dashboard home** renders on `/` (a greeting, stat cards, a Live-now column,
|
|
3313
3359
|
> a Recent artefacts card, and a Channels column), consuming existing endpoints
|
|
@@ -4244,13 +4290,13 @@ Standing rules the operator gives over a channel are `Preference` nodes. Two mec
|
|
|
4244
4290
|
|
|
4245
4291
|
**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.
|
|
4246
4292
|
|
|
4247
|
-
**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
|
|
4293
|
+
**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.
|
|
4248
4294
|
|
|
4249
4295
|
**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`.
|
|
4250
4296
|
|
|
4251
4297
|
**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.
|
|
4252
4298
|
|
|
4253
|
-
**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-
|
|
4299
|
+
**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.
|
|
4254
4300
|
|
|
4255
4301
|
**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.
|
|
4256
4302
|
|