@rubytech/create-maxy-code 0.1.290 → 0.1.292
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/dist/index.js +14 -0
- package/package.json +1 -1
- package/payload/platform/lib/graph-style/dist/index.d.ts +2 -1
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +14 -1
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/index.ts +15 -1
- package/payload/platform/package.json +3 -2
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +4 -2
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +21 -0
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +4 -0
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +28 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +6 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +65 -3
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +8 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts +27 -0
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js +45 -0
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-store.d.ts +29 -0
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-store.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-store.js +121 -0
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-store.js.map +1 -0
- package/payload/platform/services/webchat-channel/dist/notification.d.ts +55 -0
- package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -0
- package/payload/platform/services/webchat-channel/dist/notification.js +59 -0
- package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -0
- package/payload/platform/services/webchat-channel/dist/server.d.ts +3 -0
- package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -0
- package/payload/platform/services/webchat-channel/dist/server.js +179 -0
- package/payload/platform/services/webchat-channel/dist/server.js.map +1 -0
- package/payload/platform/services/webchat-channel/package.json +20 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +20 -15
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +23 -19
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/server/{chunk-EAJMRICW.js → chunk-QDCBAHPI.js} +1 -1
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/AdminShell-CpRQprdb.js +1 -0
- package/payload/server/public/assets/{Checkbox-Dmc64qme.js → Checkbox-hYA5LjZ5.js} +1 -1
- package/payload/server/public/assets/SidebarSplitter-D0mErwLL.js +1 -0
- package/payload/server/public/assets/Transcript-BaC574J0.js +1 -0
- package/payload/server/public/assets/admin-BVasculM.js +1 -0
- package/payload/server/public/assets/{arc-BW1WhwmV.js → arc-yRinR5h4.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-B2UAw-4R.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-NkTPStkv.js → architectureDiagram-Q4EWVU46-d4iT6VnY.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DKsRoptY.js → blockDiagram-DXYQGD6D-o5Tjd8VR.js} +1 -1
- package/payload/server/public/assets/browser-CwU2ReNI.js +1 -0
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-4SSchEmY.js → c4Diagram-AHTNJAMY-B8IQ2a83.js} +1 -1
- package/payload/server/public/assets/channel-CizJU0ud.js +1 -0
- package/payload/server/public/assets/chat-NxS8JsVQ.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-CKn-bEkI.js → chunk-2KRD3SAO-DAJbbBPY.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-C8szFVD5.js → chunk-336JU56O-1-fVU2_7.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-CAPR5ZG1.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-B2mSmC97.js → chunk-4BX2VUAB-BTCTbhtf.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-YsoBMSBN.js → chunk-4TB4RGXK-k-V-xUC9.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-JLT9m7Hd.js → chunk-55IACEB6-BY026nUv.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-fvCSUwuQ.js → chunk-5FUZZQ4R-DMLY3acx.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-BKCeZJLg.js → chunk-5PVQY5BW-Dw-Sd9tl.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-CCdP3Pdx.js → chunk-67CJDMHE-Dl7bLJGe.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-bXgVzdM9.js → chunk-7N4EOEYR-CFBOyqnD.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-CYeDvY8B.js → chunk-AA7GKIK3-BnnqjSlh.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-U0Fi-Ers.js → chunk-BSJP7CBP-DKCTDAZE.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-C8QrV6Gg.js → chunk-CIAEETIT-jjeAEkQy.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-DhpopONL.js → chunk-EDXVE4YY-DtpX8vYJ.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-CwpQJ9IE.js → chunk-ENJZ2VHE-TxT7_-ER.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-DxAQS3UB.js → chunk-FMBD7UC4-B0zglVlf.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-CyIK3zeY.js → chunk-FOC6F5B3-COVX_UuK.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-D5p7fCDj.js → chunk-ICPOFSXX-CPrjaFl6.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-7GcDzjyJ.js → chunk-K5T4RW27-Bav1KRIS.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-B7pAr9hf.js → chunk-KGLVRYIC-nj7A12cJ.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-DEoU2jrz.js → chunk-LIHQZDEY-BmMkL9jD.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-BwMFuN24.js → chunk-ORNJ4GCN-BvGwrI3j.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-DCXhGWX0.js → chunk-OYMX7WX6-CmoWgYtj.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-COXR_N4N.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-D6lKmEsB.js → chunk-U2HBQHQK-stvjyyuR.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-DiKHy0ar.js → chunk-X2U36JSP-qAuy5Y44.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-BA0a8Ygs.js → chunk-XPW4576I-DpLw5_cw.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-AqHm3K1O.js → chunk-YZCP3GAM-Vk12yILs.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-jDPov-Ao.js → chunk-ZZ45TVLE-WJVrlWz_.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-kzz9ab1y.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-NFfOmc0C.js +1 -0
- package/payload/server/public/assets/clone-C15qvnbX.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-Df_JmB8E.js → cose-bilkent-S5V4N54A-bCIQHniJ.js} +1 -1
- package/payload/server/public/assets/{dagre-Dyawd5Z2.js → dagre--B-g8MLT.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-DDGXoEYU.js → dagre-KV5264BT-BHFfoLq0.js} +1 -1
- package/payload/server/public/assets/data-CebDWao6.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-BQ1s5wpz.js → diagram-5BDNPKRD-TvCFSaXd.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-BnBA0Ain.js → diagram-G4DWMVQ6-8flGaFB_.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-BrGASMP1.js → diagram-MMDJMWI5-Dag0WBhV.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-iSPhqW2Q.js → diagram-TYMM5635-u7wh2Nwj.js} +1 -1
- package/payload/server/public/assets/{dist-BmvZ8OaX.js → dist-ePW3hz19.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-BCRyIkOj.js → erDiagram-SMLLAGMA-DBTHDFh_.js} +1 -1
- package/payload/server/public/assets/{flatten-Bo6YRmWl.js → flatten-CpKIi5d2.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BgMtd8aD.js → flowDiagram-DWJPFMVM-C4bd2cb-.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BKpI7Sqx.js → ganttDiagram-T4ZO3ILL-DrfpI4Gl.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CwwaM7ed.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CEx6x6gJ.js → gitGraphDiagram-UUTBAWPF-a_WyzWRQ.js} +1 -1
- package/payload/server/public/assets/graph-labels-DznU92XA.js +1 -0
- package/payload/server/public/assets/{graph-DDJ3p_3N.js → graph-qR6IbCv1.js} +19 -19
- package/payload/server/public/assets/{graphlib-Do8h5YVR.js → graphlib-B85zmy3s.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-CiiU112m.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-g9Re1Ubl.js +2 -0
- package/payload/server/public/assets/{isEmpty-D6Kr-M1M.js → isEmpty-C3Vxk1It.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BmCNGBwK.js → ishikawaDiagram-UXIWVN3A-3jXIqGtb.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-C_37fL2K.js → journeyDiagram-VCZTEJTY-BXrBb8KE.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BLFxAQHd.js → kanban-definition-6JOO6SKY-Dc5vZnuT.js} +1 -1
- package/payload/server/public/assets/{line-FtY_Ltj6.js → line-x0RuVReA.js} +1 -1
- package/payload/server/public/assets/{linear-COm19-p7.js → linear-Cdt0EBMa.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-BIr5bg_O.js → mermaid-parser.core-CBRGDK2w.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-8XmmGzXJ.js → mermaid.core-DeYYeYyM.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DPKDmfiH.js → mindmap-definition-QFDTVHPH-Bw-hRp-Y.js} +1 -1
- package/payload/server/public/assets/{ordinal-BDi6f4xk.js → ordinal-BLrOss5K.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-Bq_zPNb3.js +1 -0
- package/payload/server/public/assets/pie-ZZUOXDRM-R-cewHYM.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-DyuI0jFr.js → pieDiagram-DEJITSTG-C2DTjmdf.js} +1 -1
- package/payload/server/public/assets/{public-n0sHV8JI.js → public-tk3PfPWF.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-B24Cvsjf.js → quadrantDiagram-34T5L4WZ-Ffhnv5Ih.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-CZpBUjmo.js +1 -0
- package/payload/server/public/assets/{reduce-CGi9ik8i.js → reduce-C5tBOlxC.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-DpieLND2.js → requirementDiagram-MS252O5E-y7-gOSmI.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DFSI4LuL.js → sankeyDiagram-XADWPNL6-CMwArh7T.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-3babM9wY.js → sequenceDiagram-FGHM5R23-C8obTGl1.js} +1 -1
- package/payload/server/public/assets/{src-DLEIsjER.js → src-DssjGmtU.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DKS3fdXm.js → stateDiagram-FHFEXIEX-BZHsj7kJ.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-fK1TrvKX.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-1nRSO-Z9.js → timeline-definition-GMOUNBTQ-D-fo_L7-.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CjsdVrjr.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-BZ8peN4O.js +1 -0
- package/payload/server/public/assets/{useSelectionMode-DvBm4di-.js → useSelectionMode-CJ2UADOe.js} +1 -1
- package/payload/server/public/assets/useSelectionMode-CQBRZjvc.css +1 -0
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Ds6i6j3g.js → vennDiagram-DHZGUBPP--aEWsS70.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-BYLmRSDV.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-IZMQXgw6.js → wardleyDiagram-NUSXRM2D-DFWrB0Kk.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BGZkTooo.js → xychartDiagram-5P7HB3ND-DZsTZHC6.js} +1 -1
- package/payload/server/public/browser.html +6 -5
- package/payload/server/public/chat.html +19 -0
- package/payload/server/public/data.html +7 -6
- package/payload/server/public/graph.html +8 -7
- package/payload/server/public/index.html +8 -6
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +787 -340
- package/payload/server/public/assets/AdminShell-CnslFKoX.js +0 -1
- package/payload/server/public/assets/admin-BJYDe3p8.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-xHbVjatf.js +0 -1
- package/payload/server/public/assets/browser-CKxPsTlh.js +0 -1
- package/payload/server/public/assets/channel-DPRBU48c.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-CWj9Y5LE.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-CHE_iZO7.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BmQz46EF.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DyidaYtj.js +0 -1
- package/payload/server/public/assets/clone-D7qj6dUB.js +0 -1
- package/payload/server/public/assets/data-BEDsi4bU.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CmHdoS27.js +0 -1
- package/payload/server/public/assets/graph-labels-Cgd3XiNd.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-DTA9msO-.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-DGlZXVKh.js +0 -2
- package/payload/server/public/assets/packet-4T2RLAQJ-6GjqIKgu.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-T72DJ5JR.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-x-6xW1dE.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Dh0X-qyv.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-5-6wEryJ.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-DF48Cc86.js +0 -1
- package/payload/server/public/assets/useSelectionMode-CeQtw8Ba.css +0 -1
- package/payload/server/public/assets/wardley-RL74JXVD-duKIa6Hq.js +0 -1
- /package/payload/server/public/assets/{_baseFor-Cam2PbSt.js → _baseFor-WfS9pKAn.js} +0 -0
- /package/payload/server/public/assets/{array-DYRGGQae.js → array-HeX70jSN.js} +0 -0
- /package/payload/server/public/assets/{chunk-CnGqDkHZ.js → chunk-CWOGyPgy.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-nWsJMTNI.js → cytoscape.esm-CDZo0kst.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-Du1XY3Dp.js → defaultLocale-GJwWH1Jr.js} +0 -0
- /package/payload/server/public/assets/{init-B5BXBRcm.js → init-BPLPMQ3Y.js} +0 -0
- /package/payload/server/public/assets/{katex-HOUACuRw.js → katex-CKZ-HWMQ.js} +0 -0
- /package/payload/server/public/assets/{path-CNO468J-.js → path-YdFzr2W6.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-DRO6hWPh.js → rough.esm-HAx67Hnb.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -2216,6 +2216,20 @@ function buildPlatform() {
|
|
|
2216
2216
|
console.log(` Installing whatsapp-channel dependencies (${wcDir})...`);
|
|
2217
2217
|
shellRetry("npm", ["install", "--omit=dev", ...NPM_NET_FLAGS], { cwd: wcDir }, 3, 15);
|
|
2218
2218
|
}
|
|
2219
|
+
// (maxy-code, Task 772) — webchat-channel service: identical contract to
|
|
2220
|
+
// whatsapp-channel above (ships dist/ + package.json declaring
|
|
2221
|
+
// @modelcontextprotocol/sdk; the per-conversation channel server resolves
|
|
2222
|
+
// the SDK from this node_modules). Wipe + reinstall on upgrade.
|
|
2223
|
+
const webchatDir = join(INSTALL_DIR, "platform", "services", "webchat-channel");
|
|
2224
|
+
if (existsSync(webchatDir)) {
|
|
2225
|
+
const webchatNodeModules = join(webchatDir, "node_modules");
|
|
2226
|
+
if (existsSync(webchatNodeModules)) {
|
|
2227
|
+
console.log(" Wiping previous webchat-channel/node_modules for a clean reinstall...");
|
|
2228
|
+
rmSync(webchatNodeModules, { recursive: true, force: true });
|
|
2229
|
+
}
|
|
2230
|
+
console.log(` Installing webchat-channel dependencies (${webchatDir})...`);
|
|
2231
|
+
shellRetry("npm", ["install", "--omit=dev", ...NPM_NET_FLAGS], { cwd: webchatDir }, 3, 15);
|
|
2232
|
+
}
|
|
2219
2233
|
// Premium-plugin MCP servers (e.g. real-agent/loop) ship dist/ + package.json
|
|
2220
2234
|
// in the payload but no node_modules — npm pack strips them, same reason as
|
|
2221
2235
|
// server/. Discover every <installDir>/premium-plugins/<bundle>/plugins/<plugin>/mcp
|
package/package.json
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* unchanged colour-stability tests) keep the surfaces in lockstep.
|
|
13
13
|
*
|
|
14
14
|
* Sources of truth:
|
|
15
|
-
* - Label list:
|
|
15
|
+
* - Label list: platform/neo4j/schema.cypher (constraints + fulltext
|
|
16
|
+
* unions); registry doctrine in .docs/graph-label-registries.md
|
|
16
17
|
* - Brand colour for LocalBusiness: brand.json defaultColors.primary
|
|
17
18
|
* (resolved at runtime; this module supplies the schema-default fallback)
|
|
18
19
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAyFtD,CAAA;AAED,eAAO,MAAM,qBAAqB,YAAY,CAAA;AAE9C,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAE7C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAOzE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,CAU/E;AASD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAKD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIjE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CA0D1D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAmC3D"}
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* unchanged colour-stability tests) keep the surfaces in lockstep.
|
|
14
14
|
*
|
|
15
15
|
* Sources of truth:
|
|
16
|
-
* - Label list:
|
|
16
|
+
* - Label list: platform/neo4j/schema.cypher (constraints + fulltext
|
|
17
|
+
* unions); registry doctrine in .docs/graph-label-registries.md
|
|
17
18
|
* - Brand colour for LocalBusiness: brand.json defaultColors.primary
|
|
18
19
|
* (resolved at runtime; this module supplies the schema-default fallback)
|
|
19
20
|
*/
|
|
@@ -45,6 +46,18 @@ exports.GRAPH_LABEL_COLOURS = {
|
|
|
45
46
|
Property: '#5C7A99',
|
|
46
47
|
Viewing: '#D4A574',
|
|
47
48
|
Offer: '#B08850',
|
|
49
|
+
// Construction ontology (Task 773; labels registered in schema.cypher by
|
|
50
|
+
// Task 650). Muted brick-red family — the construction register sits
|
|
51
|
+
// apart from people-terracotta, viewing-amber, and task-rose bands so
|
|
52
|
+
// siteoffice legends read as their own group. Top-level (operator-entry)
|
|
53
|
+
// labels only; child labels (LineItem, Milestone, Valuation,
|
|
54
|
+
// VariationNote, TimeLog) intentionally stay fallback-grey.
|
|
55
|
+
Job: '#8C3B2E',
|
|
56
|
+
QuoteDocument: '#A65A4A',
|
|
57
|
+
InboundInvoice: '#6B2A20',
|
|
58
|
+
SubContractor: '#BF7E6E',
|
|
59
|
+
SubInvoice: '#52201A',
|
|
60
|
+
WhatsAppGroup: '#D4A096',
|
|
48
61
|
// External business entity — schema:Organization, written by memory-write
|
|
49
62
|
// as the Supplier label. Warm tobacco — distinct hue from internal-business
|
|
50
63
|
// slates so external entities are scannable at a glance.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* unchanged colour-stability tests) keep the surfaces in lockstep.
|
|
13
13
|
*
|
|
14
14
|
* Sources of truth:
|
|
15
|
-
* - Label list:
|
|
15
|
+
* - Label list: platform/neo4j/schema.cypher (constraints + fulltext
|
|
16
|
+
* unions); registry doctrine in .docs/graph-label-registries.md
|
|
16
17
|
* - Brand colour for LocalBusiness: brand.json defaultColors.primary
|
|
17
18
|
* (resolved at runtime; this module supplies the schema-default fallback)
|
|
18
19
|
*/
|
|
@@ -38,6 +39,19 @@ export const GRAPH_LABEL_COLOURS: Record<string, string> = {
|
|
|
38
39
|
Viewing: '#D4A574',
|
|
39
40
|
Offer: '#B08850',
|
|
40
41
|
|
|
42
|
+
// Construction ontology (Task 773; labels registered in schema.cypher by
|
|
43
|
+
// Task 650). Muted brick-red family — the construction register sits
|
|
44
|
+
// apart from people-terracotta, viewing-amber, and task-rose bands so
|
|
45
|
+
// siteoffice legends read as their own group. Top-level (operator-entry)
|
|
46
|
+
// labels only; child labels (LineItem, Milestone, Valuation,
|
|
47
|
+
// VariationNote, TimeLog) intentionally stay fallback-grey.
|
|
48
|
+
Job: '#8C3B2E',
|
|
49
|
+
QuoteDocument: '#A65A4A',
|
|
50
|
+
InboundInvoice: '#6B2A20',
|
|
51
|
+
SubContractor: '#BF7E6E',
|
|
52
|
+
SubInvoice: '#52201A',
|
|
53
|
+
WhatsAppGroup: '#D4A096',
|
|
54
|
+
|
|
41
55
|
// External business entity — schema:Organization, written by memory-write
|
|
42
56
|
// as the Supplier label. Warm tobacco — distinct hue from internal-business
|
|
43
57
|
// slates so external entities are scannable at a glance.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"services/*"
|
|
7
7
|
],
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
9
|
+
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
10
|
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json",
|
|
11
11
|
"gen:canonical-tools": "node scripts/generate-canonical-tool-names.mjs",
|
|
12
12
|
"build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"build:replicate": "tsc -p plugins/replicate/mcp/tsconfig.json",
|
|
23
23
|
"build:graph-viewer": "tsc -p plugins/graph-viewer/mcp/tsconfig.json",
|
|
24
24
|
"build:claude-session-manager": "tsc -p services/claude-session-manager/tsconfig.json",
|
|
25
|
-
"build:whatsapp-channel": "tsc -p services/whatsapp-channel/tsconfig.json"
|
|
25
|
+
"build:whatsapp-channel": "tsc -p services/whatsapp-channel/tsconfig.json",
|
|
26
|
+
"build:webchat-channel": "tsc -p services/webchat-channel/tsconfig.json"
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -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:edae39c038e88d2b4f86c7bfb6f9710a825515ef8ddbc1fd24ea501f38dd0dbe
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -196,7 +196,7 @@ There is no dashboard, no settings panel, no menus. Everything is done through c
|
|
|
196
196
|
|
|
197
197
|
The chat input auto-grows as you type — it expands to fit your message and shrinks back when you delete text. You can also drag the resize handle above the input to set a custom height.
|
|
198
198
|
|
|
199
|
-
The admin interface is a three-pane layout: a sidebar on the left with navigation (Sessions, People, Agents, Projects, Tasks, Artefacts) and your recent conversations; the chat in the middle; and an artefact pane on the right that opens when you select a document, click a project, or open Browser, Data, or Graph from the menu, holding the surface side-by-side with the conversation so the chat stays live while you work in it. At the very top of the sidebar — above the nav rows — a borderless row holds two controls: a "+ New session" button on the left that spawns a fresh Claude Code session, and a Mode trigger on the right showing the current permission mode (Ask, Accept edits, Plan, or Auto). The sidebar's vertical order is: new-session strip first, then the nav (Sessions, People, Agents, Projects, Tasks, Artefacts), then the sessions list, then the footer. Both controls render as plain text-plus-icon affordances with no surrounding rectangle. The "+ New session" button is a text-width hit target — its clickable area is exactly the icon plus label, not the whole row — and shows no hover fill; the only hover feedback is the pointer cursor. The Mode trigger is pushed flush to the right edge of the row. Clicking the Mode trigger opens a popover downward from the row whose header reads "Mode" and lists the four permission modes with the current selection check-marked. The sidebar's nav rows swap the list view in place: Sessions shows recent conversations, Projects shows your active work projects, and Artefacts lists every KnowledgeDocument plus this account's agent templates (your admin agent's IDENTITY, SOUL, and KNOWLEDGE files plus one entry per enabled specialist). Each recent session row carries a three-state indicator: three pulsing dots when the session is busy (currently processing a turn), a solid sage dot when it is idle (live PTY waiting for input), and a hollow ring when it is archived (PTY exited, JSONL on disk for audit). The list itself splits into three views via a segmented control above the rows: **Active** shows every live session, **Archived** shows every JSONL on disk whose PTY has exited, and **All** shows both. The view choice persists across reloads. An "Include subagents" toggle inside the Active view surfaces specialist spawns (database-operator, premium-plugin agents, anything spawned with a `--agent` flag) which are hidden by default so the list reflects what you started directly. Each row also carries a small uppercase badge — `admin` for operator-driven sessions, the specialist name (for example `db-op`) for background work — so the source of any row is unambiguous at a glance. The People, Agents, and Tasks rows are graph shortcuts: clicking each opens the artefact-pane Graph filtered to every Person, every public Agent, or every Task in your account respectively, with no side-list, because the graph itself is the result. Public agents become first-class graph entities the moment you create them, with edges to their IDENTITY/SOUL/KNOWLEDGE files, edges to every knowledge document they have access to, and edges from every conversation they have handled, so a single Agents click reveals the whole shape of who knows what and who has been talking to whom. Click an artefact row to open the document. KnowledgeDocuments and your admin agent's templates are editable: type in the document and changes save automatically; specialist agent templates are read-only because they ship with Maxy and your edits would be overwritten on the next install. PDF artefacts render inline so you can read them without leaving the pane. If your browser doesn't have a built-in PDF viewer, a Download button appears instead. Artefacts that have no readable file backing them (orphan rows, files removed from disk, unsupported content types) show a one-line banner explaining the skip instead of opening to a blank pane. Click a project row to open the Graph view focused on that project's neighbourhood; clicking a second project swaps the focus rather than stacking on top. The sidebar's right edge is drag-resizable on every admin page (Sessions root, Graph, and Data): drag the handle to widen or narrow the sidebar, and your chosen width is remembered across reloads and shared across all three pages. The drag handle is mounted by each AdminShell consumer rather than by AdminShell itself, so any new admin route must include `<SidebarSplitter />` as a direct child of its `<AdminShell>` to pick up the shared width. The chat and artefact divider is also drag-resizable: drag the line between the columns to make either side wider; double-click it to reset to half of the available width (viewport minus sidebar), clamped to the chat and artefact min-width floors. Your chosen width is remembered across reloads. On wider screens (>1280px) all three panes are visible. The sidebar narrows at 1280px, the artefact pane hides at 1080px (Browser, Data, and Graph then open as full-window pages instead), and the sidebar collapses to a 56px icon rail at 820px. On every viewport the chat header reads left to right as a triptych: a dedicated sidebar toggle (the panel-right icon, which swaps to panel-right-open when the sidebar is showing), the brand mark next to the title in the centre, and the menu burger on the right. This header toggle is the sole sidebar-toggle button; the sidebar itself no longer carries a duplicate. Tap the sidebar toggle to show or hide the sidebar: on phones (<720px) it slides the drawer in or out, on wider screens it collapses or expands the sidebar column. The brand mark in the centre is decorative; clicks go through the dedicated toggle so the affordance is unambiguous. The drawer animation only fires on tap (220ms slide in or out); resizing your window across the 720px boundary snaps the layout without animation, so you never see a half-open flash. At ≤640px the session metadata pane stacks each label above its value instead of the desktop two-column grid, and the row of action buttons (Open in new tab / Download JSONL / View JSONL / Rename / Pin / Archive / End or Purge) collapses behind a single Actions trigger that opens a popover upward from the foot of the pane. Breakpoint summary: >1280px = full sidebar + chat + artefact pane (drag-resizable divider); 1280px→1080px = sidebar narrows; 1080px→820px = artefact pane hides (Browser/Data/Graph open as full-window pages instead); 820px→720px = sidebar collapses to 56px icon rail; ≤720px = sidebar becomes off-canvas drawer (vertical stack of nav, recents list, foot, the same shape as the desktop sidebar, just on top of the chat instead of beside it).
|
|
199
|
+
The admin interface is a three-pane layout: a sidebar on the left with navigation (Sessions, People, Agents, Projects, Tasks, Artefacts) and your recent conversations; the chat in the middle; and an artefact pane on the right that opens when you select a document, click a project, or open Browser, Data, or Graph from the menu, holding the surface side-by-side with the conversation so the chat stays live while you work in it. At the very top of the sidebar — above the nav rows — a borderless row holds two controls: a "+ New session" button on the left that spawns a fresh Claude Code session, and a Mode trigger on the right showing the current permission mode (Ask, Accept edits, Plan, or Auto). The sidebar's vertical order is: new-session strip first, then the nav (Sessions, People, Agents, Projects, Tasks, Artefacts), then the sessions list, then the footer. Both controls render as plain text-plus-icon affordances with no surrounding rectangle. The "+ New session" button is a text-width hit target — its clickable area is exactly the icon plus label, not the whole row — and shows no hover fill; the only hover feedback is the pointer cursor. The Mode trigger is pushed flush to the right edge of the row. Clicking the Mode trigger opens a popover downward from the row whose header reads "Mode" and lists the four permission modes with the current selection check-marked. The sidebar's nav rows swap the list view in place: Sessions shows recent conversations, Projects shows your active work projects, and Artefacts lists every KnowledgeDocument plus this account's agent templates (your admin agent's IDENTITY, SOUL, and KNOWLEDGE files plus one entry per enabled specialist). Each recent session row carries a three-state indicator: three pulsing dots when the session is busy (currently processing a turn), a solid sage dot when it is idle (live PTY waiting for input), and a hollow ring when it is archived (PTY exited, JSONL on disk for audit). The list itself splits into three views via a segmented control above the rows: **Active** shows every live session, **Archived** shows every JSONL on disk whose PTY has exited, and **All** shows both. The view choice persists across reloads. An "Include subagents" toggle inside the Active view surfaces specialist spawns (database-operator, premium-plugin agents, anything spawned with a `--agent` flag) which are hidden by default so the list reflects what you started directly. Each row also carries a small uppercase badge — `admin` for operator-driven sessions, the specialist name (for example `db-op`) for background work — so the source of any row is unambiguous at a glance. The People, Agents, and Tasks rows are graph shortcuts: clicking each opens the artefact-pane Graph filtered to every Person, every public Agent, or every Task in your account respectively, with no side-list, because the graph itself is the result. Public agents become first-class graph entities the moment you create them, with edges to their IDENTITY/SOUL/KNOWLEDGE files, edges to every knowledge document they have access to, and edges from every conversation they have handled, so a single Agents click reveals the whole shape of who knows what and who has been talking to whom. Click an artefact row to open the document. KnowledgeDocuments and your admin agent's templates are editable: type in the document and changes save automatically; specialist agent templates are read-only because they ship with Maxy and your edits would be overwritten on the next install. PDF artefacts render inline so you can read them without leaving the pane. If your browser doesn't have a built-in PDF viewer, a Download button appears instead. Artefacts that have no readable file backing them (orphan rows, files removed from disk, unsupported content types) show a one-line banner explaining the skip instead of opening to a blank pane. Click a project row to open the Graph view focused on that project's neighbourhood; clicking a second project swaps the focus rather than stacking on top. The sidebar's right edge is drag-resizable on every admin page (Sessions root, Graph, and Data): drag the handle to widen or narrow the sidebar, and your chosen width is remembered across reloads and shared across all three pages. The drag handle is mounted by each AdminShell consumer rather than by AdminShell itself, so any new admin route must include `<SidebarSplitter />` as a direct child of its `<AdminShell>` to pick up the shared width. The chat and artefact divider is also drag-resizable: drag the line between the columns to make either side wider; double-click it to reset to half of the available width (viewport minus sidebar), clamped to the chat and artefact min-width floors. Your chosen width is remembered across reloads. On wider screens (>1280px) all three panes are visible. The sidebar narrows at 1280px, the artefact pane hides at 1080px (Browser, Data, and Graph then open as full-window pages instead), and the sidebar collapses to a 56px icon rail at 820px. On every viewport the chat header reads left to right as a triptych: a dedicated sidebar toggle (the panel-right icon, which swaps to panel-right-open when the sidebar is showing), the brand mark next to the title in the centre, and the menu burger on the right. This header toggle is the sole sidebar-toggle button; the sidebar itself no longer carries a duplicate. The burger opens the navigation flyout: Dashboard, Data, Graph, Browser, and Chat at the top — Dashboard routes back to `/`, so every satellite page keeps a one-click way home, and the Chat item carries the Claude mark rather than a generic icon — then Public (the agents list) between the two dividers, and below the second divider the version row, Disconnect Claude account, and Log out. Tap the sidebar toggle to show or hide the sidebar: on phones (<720px) it slides the drawer in or out, on wider screens it collapses or expands the sidebar column. The brand mark in the centre is decorative; clicks go through the dedicated toggle so the affordance is unambiguous. The drawer animation only fires on tap (220ms slide in or out); resizing your window across the 720px boundary snaps the layout without animation, so you never see a half-open flash. At ≤640px the session metadata pane stacks each label above its value instead of the desktop two-column grid, and the row of action buttons (Open in new tab / Download JSONL / View JSONL / Rename / Pin / Archive / End or Purge) collapses behind a single Actions trigger that opens a popover upward from the foot of the pane. Breakpoint summary: >1280px = full sidebar + chat + artefact pane (drag-resizable divider); 1280px→1080px = sidebar narrows; 1080px→820px = artefact pane hides (Browser/Data/Graph open as full-window pages instead); 820px→720px = sidebar collapses to 56px icon rail; ≤720px = sidebar becomes off-canvas drawer (vertical stack of nav, recents list, foot, the same shape as the desktop sidebar, just on top of the chat instead of beside it).
|
|
200
200
|
|
|
201
201
|
Page titles are brand-aware: the browser tab shows your product name (e.g. `Real Agent` instead of `Maxy`) on every shell — chat, graph, and data — so a non-default brand never leaks the default name in tab strips or browser history.
|
|
202
202
|
|
|
@@ -3814,6 +3814,8 @@ tail -200 ~/.maxy/logs/maxy-ui.log | rg '\[remote-auth\].*resolvedKind='
|
|
|
3814
3814
|
2. `diff <(jq .claudeAiOauth.accessToken ~/.maxy/.claude/.credentials.json) <(jq .claudeAiOauth.accessToken ~/.realagent/.claude/.credentials.json)` — must be non-empty after each brand's operator has run `claude /login` against distinct Anthropic accounts; if it's empty, both brands are still logged in to the same account (operator action, not a code bug).
|
|
3815
3815
|
3. `grep "\[install\] claude-creds pickup" ~/.${brand}/logs/install-*.log` — fires once on the first post-Task-923 install of any brand and moves the legacy `~/.claude/.credentials.json` into that brand's path. Subsequent brands install with no credentials and require a fresh `claude /login` inside that brand's chat (which writes to the brand-scoped path because the systemd unit env is in scope).
|
|
3816
3816
|
|
|
3817
|
+
To deliberately disconnect the wrong account, use the header menu's **Disconnect Claude account** item (the supported path; no SSH or manual file deletion needed). It runs `claude auth logout`, then verifies the brand's `~/${brand.configDir}/.claude/.credentials.json` is actually gone before reporting success — a disconnect that left the credential in place reports failure in the menu rather than false-confirming. The server logs one line per attempt: `grep '\[onboarding\] op=claude-logout' ~/.${brand}/logs/server.log` — `credentialsPresent=true` after the call is a failed disconnect the operator may believe succeeded.
|
|
3818
|
+
|
|
3817
3819
|
**All sessions on the brand stopped responding after a token expiry.** Symptom on the operator side: every spawn dies at `pid-file-timeout` and the dashboard health probe reports auth dead. Diagnose the OAuth refresh path before anything else:
|
|
3818
3820
|
|
|
3819
3821
|
1. `tail -n 300 ~/.${brand}/logs/server.log | grep -E 'auth-refresh|auth-health|invalid_grant'` — `op=lock-acquired` proves the cross-process lock is in play (Task 576). `op=skipped-fresh` means a sibling process (the admin server or a `claude` binary) already rotated the tokens during the lock wait — expected, healthy. `op=renewed expiresAt=…` is the only line that means a network refresh actually ran.
|
|
@@ -106,12 +106,31 @@ CLOUDFLARE_API_TOKEN="${MINTED_ACCESS}" curl -sS \
|
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
2. **Create the Access application** scoped to the deployed host's **document path(s) `and` `/api/*`** — the signing document's path(s) plus the capture endpoints `POST /api/accept` and `GET /api/status` (§ 4). Leave `/` and the static assets **public**. Gating only the document path is a **bypass**: with `/api/*` open, anyone can `POST /api/accept` and forge an acceptance, and `GET /api/status` is readable ungated. Leaving `/` public is deliberate and load-bearing — it is exactly what lets the neutral root portal (below) load **without a PIN**; the document path(s) and `/api/*` are the only gated destinations.
|
|
109
|
+
|
|
110
|
+
**Same-origin invariant — `/api/*` must be Functions of *this* Pages project.** An Access application gates a **host + path**, never a bare path across hosts. So gating `<host>/api/*` covers `/api/accept` and `/api/status` **only when they are Pages Functions of the same Pages project — the same origin — as the gated document**. The § 2 form posts to the **relative** `/api/accept` and `/api/status`, which keeps them same-origin by construction; that default is the invariant, not a convenience. **Splitting the endpoints onto a separate origin** — a second Pages project, any other host — escapes the gate entirely while every per-document check still passes: the document 302s to login, but `POST /api/accept` on the other origin is **forgeable** by anyone and `GET /api/status` is **world-readable**, because the document's Access app never covered that host. The `gate-probe` of "Verify on live" (below) is the only check that catches this; the per-page redirect does not. If the deploy ever resolves `/api/*` to an origin other than the document's own, that origin mismatch is itself the failure — before any probe runs.
|
|
109
111
|
3. **Attach a policy** whose `include` allowlist names the **signer's email** (and the business owner's). Only those addresses can authenticate and view the page.
|
|
110
112
|
|
|
111
113
|
The IdP call shapes are given inline above; the Access **app** and **policy** endpoints live in `api.md`'s endpoint map, and the permission-group ids are resolved by the `api.md` method (`GET …/tokens/permission_groups`).
|
|
112
114
|
|
|
113
115
|
**Verify on live.** Loading the gated page **302-redirects to a Cloudflare login page that offers One-Time PIN** — not an empty dead-end. An allowlisted email receives a code, enters it, and reaches the document; a non-allowlisted email is refused after entering its code. Reproduction-before: with **zero** IdPs the same login page offers no method at all — that is the lockout (§ 8.9).
|
|
114
116
|
|
|
117
|
+
**Verify on live — `gate-probe` `/api/*`, not just the page.** The page redirect above is necessary but **not sufficient**: it never touches `/api/*`, which is exactly how a separate-origin API (the same-origin invariant, above) deploys green while wide open. Before a gated deploy is "done", hit **both** capture endpoints **unauthenticated, with no Access session**, against the live API origin, and read the status code — each must be **refused** (302 to the Access login, or 403) before it reaches the Function:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# No cookies, no Access session. API MUST be the gated document's OWN origin —
|
|
121
|
+
# the canonical host § 7 writes as <project>.pages.dev, or a custom domain mapped to it.
|
|
122
|
+
# An UNGATED accept endpoint inserts a sentinel row (doc_ref=GATE-PROBE); delete it as
|
|
123
|
+
# part of remediation. A gated endpoint 302s before the Function runs, so nothing is written.
|
|
124
|
+
API="https://<host>"
|
|
125
|
+
printf 'op=gate-probe endpoint=/api/accept method=POST code=%s\n' \
|
|
126
|
+
"$(curl -sS -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' \
|
|
127
|
+
-d '{"name":"probe","email":"probe@example.com","token":"GATE-PROBE-aaaaa-aaaaaa","doc_ref":"GATE-PROBE"}' "$API/api/accept")"
|
|
128
|
+
printf 'op=gate-probe endpoint=/api/status method=GET code=%s\n' \
|
|
129
|
+
"$(curl -sS -o /dev/null -w '%{http_code}' "$API/api/status?doc_ref=GATE-PROBE")"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Pass:** both `gate-probe` lines report `302` or `403` — the Access gate refused each request **before** it reached the Function. **Fail:** **any other status.** `200` is the canonical signature (`POST /api/accept` forgeable, `GET /api/status` world-readable), but `400`, `409`, or `500` equally fail — any Function-originated response proves the request **reached the Function unauthenticated**, i.e. the API is ungated. (A repeat accept probe returns `409` on the sentinel's `doc_ref` UNIQUE — still ungated, still a fail; only `302`/`403` is a pass.) The cause is almost always `/api/*` resolving to a different origin than the gated document. A non-`302`/`403` here — not the page redirect — is the only signal that catches the split-origin escape. This `gate-probe` is the **unauthenticated negative** test; it is distinct from the § 7 outcome contract, which exercises the *authenticated functional* flow (POST → D1 row → status flip) and therefore runs pre-gate or from an allowlisted session. After both probes refuse, confirm an **allowlisted** session still reaches the form and accepts once.
|
|
133
|
+
|
|
115
134
|
**Dispatch is unaffected (§ 6).** The sweep stamps the **persisted base** (`base.pdf`, § 1), never by fetching the gated page, so a gate never turns a dispatched "PDF" into an Access login screen — § 8.3 makes that structural. Keep it in mind whenever a gated document is swept.
|
|
116
135
|
|
|
117
136
|
**Logout links carry `returnTo` — point them at the public root (the primary loop-fix).** Cloudflare Access **does** honour a logout redirect: `GET /cdn-cgi/access/logout?returnTo=<https origin root>` 302s straight to that root and **strips** the stale `__cf_access_message` param. The parameter is **camelCase `returnTo` only** — `return_to` and `redirect_url` are ignored. So build every signer-facing logout link as `/cdn-cgi/access/logout?returnTo=https://<host>/`, with `returnTo` pointing at the **public root**, never at the gated document path (which would just re-challenge for a PIN). A logout link built this way lands the signer cleanly on the public root with no stale param — this is what actually breaks the "enter PIN → logged-out screen" loop.
|
|
@@ -168,6 +187,8 @@ Confirming A4 *dimensions* is not proof of fit — **read every rendered page**
|
|
|
168
187
|
|
|
169
188
|
**Acceptance token** — `<DOC_REF>-<5-char base36 timestamp>-<6-char djb2 hash>`. The hash is djb2 over `name + email + DOC_REF`: it binds the signer's identity to this document, so a UNIQUE collision on the token means *the same signer re-submitting the same document* — a genuine same-signer duplicate, distinct from a *different* signer hitting an already-accepted document (which the `doc_ref` guard catches; § 4).
|
|
170
189
|
|
|
190
|
+
**The djb2 hash is not an access control.** It is a deterministic function of `name`, `email` and `DOC_REF` — all public-ish fields a forger can supply — so it is **defence-in-depth obfuscation of the token shape, never a gate**. It makes a token mildly unguessable and lets the database discriminate a same-signer re-submit (above); it does **not** authenticate the POST. The only thing that stops a forged `POST /api/accept` is the § 1a Access gate over a **same-origin** `/api/*`. Never treat the token as a substitute for gating the API.
|
|
191
|
+
|
|
171
192
|
All confirmation, closed-state, and fallback panels are built with `textContent` and DOM nodes, never `innerHTML` — the signer's own name and email flow back into the page, and `innerHTML` would make that an injection surface.
|
|
172
193
|
|
|
173
194
|
```html
|
|
@@ -82,7 +82,7 @@ There is no dashboard, no settings panel, no menus. Everything is done through c
|
|
|
82
82
|
|
|
83
83
|
The chat input auto-grows as you type — it expands to fit your message and shrinks back when you delete text. You can also drag the resize handle above the input to set a custom height.
|
|
84
84
|
|
|
85
|
-
The admin interface is a three-pane layout: a sidebar on the left with navigation (Sessions, People, Agents, Projects, Tasks, Artefacts) and your recent conversations; the chat in the middle; and an artefact pane on the right that opens when you select a document, click a project, or open Browser, Data, or Graph from the menu, holding the surface side-by-side with the conversation so the chat stays live while you work in it. At the very top of the sidebar — above the nav rows — a borderless row holds two controls: a "+ New session" button on the left that spawns a fresh Claude Code session, and a Mode trigger on the right showing the current permission mode (Ask, Accept edits, Plan, or Auto). The sidebar's vertical order is: new-session strip first, then the nav (Sessions, People, Agents, Projects, Tasks, Artefacts), then the sessions list, then the footer. Both controls render as plain text-plus-icon affordances with no surrounding rectangle. The "+ New session" button is a text-width hit target — its clickable area is exactly the icon plus label, not the whole row — and shows no hover fill; the only hover feedback is the pointer cursor. The Mode trigger is pushed flush to the right edge of the row. Clicking the Mode trigger opens a popover downward from the row whose header reads "Mode" and lists the four permission modes with the current selection check-marked. The sidebar's nav rows swap the list view in place: Sessions shows recent conversations, Projects shows your active work projects, and Artefacts lists every KnowledgeDocument plus this account's agent templates (your admin agent's IDENTITY, SOUL, and KNOWLEDGE files plus one entry per enabled specialist). Each recent session row carries a three-state indicator: three pulsing dots when the session is busy (currently processing a turn), a solid sage dot when it is idle (live PTY waiting for input), and a hollow ring when it is archived (PTY exited, JSONL on disk for audit). The list itself splits into three views via a segmented control above the rows: **Active** shows every live session, **Archived** shows every JSONL on disk whose PTY has exited, and **All** shows both. The view choice persists across reloads. An "Include subagents" toggle inside the Active view surfaces specialist spawns (database-operator, premium-plugin agents, anything spawned with a `--agent` flag) which are hidden by default so the list reflects what you started directly. Each row also carries a small uppercase badge — `admin` for operator-driven sessions, the specialist name (for example `db-op`) for background work — so the source of any row is unambiguous at a glance. The People, Agents, and Tasks rows are graph shortcuts: clicking each opens the artefact-pane Graph filtered to every Person, every public Agent, or every Task in your account respectively, with no side-list, because the graph itself is the result. Public agents become first-class graph entities the moment you create them, with edges to their IDENTITY/SOUL/KNOWLEDGE files, edges to every knowledge document they have access to, and edges from every conversation they have handled, so a single Agents click reveals the whole shape of who knows what and who has been talking to whom. Click an artefact row to open the document. KnowledgeDocuments and your admin agent's templates are editable: type in the document and changes save automatically; specialist agent templates are read-only because they ship with Maxy and your edits would be overwritten on the next install. PDF artefacts render inline so you can read them without leaving the pane. If your browser doesn't have a built-in PDF viewer, a Download button appears instead. Artefacts that have no readable file backing them (orphan rows, files removed from disk, unsupported content types) show a one-line banner explaining the skip instead of opening to a blank pane. Click a project row to open the Graph view focused on that project's neighbourhood; clicking a second project swaps the focus rather than stacking on top. The sidebar's right edge is drag-resizable on every admin page (Sessions root, Graph, and Data): drag the handle to widen or narrow the sidebar, and your chosen width is remembered across reloads and shared across all three pages. The drag handle is mounted by each AdminShell consumer rather than by AdminShell itself, so any new admin route must include `<SidebarSplitter />` as a direct child of its `<AdminShell>` to pick up the shared width. The chat and artefact divider is also drag-resizable: drag the line between the columns to make either side wider; double-click it to reset to half of the available width (viewport minus sidebar), clamped to the chat and artefact min-width floors. Your chosen width is remembered across reloads. On wider screens (>1280px) all three panes are visible. The sidebar narrows at 1280px, the artefact pane hides at 1080px (Browser, Data, and Graph then open as full-window pages instead), and the sidebar collapses to a 56px icon rail at 820px. On every viewport the chat header reads left to right as a triptych: a dedicated sidebar toggle (the panel-right icon, which swaps to panel-right-open when the sidebar is showing), the brand mark next to the title in the centre, and the menu burger on the right. This header toggle is the sole sidebar-toggle button; the sidebar itself no longer carries a duplicate. Tap the sidebar toggle to show or hide the sidebar: on phones (<720px) it slides the drawer in or out, on wider screens it collapses or expands the sidebar column. The brand mark in the centre is decorative; clicks go through the dedicated toggle so the affordance is unambiguous. The drawer animation only fires on tap (220ms slide in or out); resizing your window across the 720px boundary snaps the layout without animation, so you never see a half-open flash. At ≤640px the session metadata pane stacks each label above its value instead of the desktop two-column grid, and the row of action buttons (Open in new tab / Download JSONL / View JSONL / Rename / Pin / Archive / End or Purge) collapses behind a single Actions trigger that opens a popover upward from the foot of the pane. Breakpoint summary: >1280px = full sidebar + chat + artefact pane (drag-resizable divider); 1280px→1080px = sidebar narrows; 1080px→820px = artefact pane hides (Browser/Data/Graph open as full-window pages instead); 820px→720px = sidebar collapses to 56px icon rail; ≤720px = sidebar becomes off-canvas drawer (vertical stack of nav, recents list, foot, the same shape as the desktop sidebar, just on top of the chat instead of beside it).
|
|
85
|
+
The admin interface is a three-pane layout: a sidebar on the left with navigation (Sessions, People, Agents, Projects, Tasks, Artefacts) and your recent conversations; the chat in the middle; and an artefact pane on the right that opens when you select a document, click a project, or open Browser, Data, or Graph from the menu, holding the surface side-by-side with the conversation so the chat stays live while you work in it. At the very top of the sidebar — above the nav rows — a borderless row holds two controls: a "+ New session" button on the left that spawns a fresh Claude Code session, and a Mode trigger on the right showing the current permission mode (Ask, Accept edits, Plan, or Auto). The sidebar's vertical order is: new-session strip first, then the nav (Sessions, People, Agents, Projects, Tasks, Artefacts), then the sessions list, then the footer. Both controls render as plain text-plus-icon affordances with no surrounding rectangle. The "+ New session" button is a text-width hit target — its clickable area is exactly the icon plus label, not the whole row — and shows no hover fill; the only hover feedback is the pointer cursor. The Mode trigger is pushed flush to the right edge of the row. Clicking the Mode trigger opens a popover downward from the row whose header reads "Mode" and lists the four permission modes with the current selection check-marked. The sidebar's nav rows swap the list view in place: Sessions shows recent conversations, Projects shows your active work projects, and Artefacts lists every KnowledgeDocument plus this account's agent templates (your admin agent's IDENTITY, SOUL, and KNOWLEDGE files plus one entry per enabled specialist). Each recent session row carries a three-state indicator: three pulsing dots when the session is busy (currently processing a turn), a solid sage dot when it is idle (live PTY waiting for input), and a hollow ring when it is archived (PTY exited, JSONL on disk for audit). The list itself splits into three views via a segmented control above the rows: **Active** shows every live session, **Archived** shows every JSONL on disk whose PTY has exited, and **All** shows both. The view choice persists across reloads. An "Include subagents" toggle inside the Active view surfaces specialist spawns (database-operator, premium-plugin agents, anything spawned with a `--agent` flag) which are hidden by default so the list reflects what you started directly. Each row also carries a small uppercase badge — `admin` for operator-driven sessions, the specialist name (for example `db-op`) for background work — so the source of any row is unambiguous at a glance. The People, Agents, and Tasks rows are graph shortcuts: clicking each opens the artefact-pane Graph filtered to every Person, every public Agent, or every Task in your account respectively, with no side-list, because the graph itself is the result. Public agents become first-class graph entities the moment you create them, with edges to their IDENTITY/SOUL/KNOWLEDGE files, edges to every knowledge document they have access to, and edges from every conversation they have handled, so a single Agents click reveals the whole shape of who knows what and who has been talking to whom. Click an artefact row to open the document. KnowledgeDocuments and your admin agent's templates are editable: type in the document and changes save automatically; specialist agent templates are read-only because they ship with Maxy and your edits would be overwritten on the next install. PDF artefacts render inline so you can read them without leaving the pane. If your browser doesn't have a built-in PDF viewer, a Download button appears instead. Artefacts that have no readable file backing them (orphan rows, files removed from disk, unsupported content types) show a one-line banner explaining the skip instead of opening to a blank pane. Click a project row to open the Graph view focused on that project's neighbourhood; clicking a second project swaps the focus rather than stacking on top. The sidebar's right edge is drag-resizable on every admin page (Sessions root, Graph, and Data): drag the handle to widen or narrow the sidebar, and your chosen width is remembered across reloads and shared across all three pages. The drag handle is mounted by each AdminShell consumer rather than by AdminShell itself, so any new admin route must include `<SidebarSplitter />` as a direct child of its `<AdminShell>` to pick up the shared width. The chat and artefact divider is also drag-resizable: drag the line between the columns to make either side wider; double-click it to reset to half of the available width (viewport minus sidebar), clamped to the chat and artefact min-width floors. Your chosen width is remembered across reloads. On wider screens (>1280px) all three panes are visible. The sidebar narrows at 1280px, the artefact pane hides at 1080px (Browser, Data, and Graph then open as full-window pages instead), and the sidebar collapses to a 56px icon rail at 820px. On every viewport the chat header reads left to right as a triptych: a dedicated sidebar toggle (the panel-right icon, which swaps to panel-right-open when the sidebar is showing), the brand mark next to the title in the centre, and the menu burger on the right. This header toggle is the sole sidebar-toggle button; the sidebar itself no longer carries a duplicate. The burger opens the navigation flyout: Dashboard, Data, Graph, Browser, and Chat at the top — Dashboard routes back to `/`, so every satellite page keeps a one-click way home, and the Chat item carries the Claude mark rather than a generic icon — then Public (the agents list) between the two dividers, and below the second divider the version row, Disconnect Claude account, and Log out. Tap the sidebar toggle to show or hide the sidebar: on phones (<720px) it slides the drawer in or out, on wider screens it collapses or expands the sidebar column. The brand mark in the centre is decorative; clicks go through the dedicated toggle so the affordance is unambiguous. The drawer animation only fires on tap (220ms slide in or out); resizing your window across the 720px boundary snaps the layout without animation, so you never see a half-open flash. At ≤640px the session metadata pane stacks each label above its value instead of the desktop two-column grid, and the row of action buttons (Open in new tab / Download JSONL / View JSONL / Rename / Pin / Archive / End or Purge) collapses behind a single Actions trigger that opens a popover upward from the foot of the pane. Breakpoint summary: >1280px = full sidebar + chat + artefact pane (drag-resizable divider); 1280px→1080px = sidebar narrows; 1080px→820px = artefact pane hides (Browser/Data/Graph open as full-window pages instead); 820px→720px = sidebar collapses to 56px icon rail; ≤720px = sidebar becomes off-canvas drawer (vertical stack of nav, recents list, foot, the same shape as the desktop sidebar, just on top of the chat instead of beside it).
|
|
86
86
|
|
|
87
87
|
Page titles are brand-aware: the browser tab shows your product name (e.g. `Real Agent` instead of `Maxy`) on every shell — chat, graph, and data — so a non-default brand never leaks the default name in tab strips or browser history.
|
|
88
88
|
|
|
@@ -121,6 +121,8 @@ tail -200 ~/.maxy/logs/maxy-ui.log | rg '\[remote-auth\].*resolvedKind='
|
|
|
121
121
|
2. `diff <(jq .claudeAiOauth.accessToken ~/.maxy/.claude/.credentials.json) <(jq .claudeAiOauth.accessToken ~/.realagent/.claude/.credentials.json)` — must be non-empty after each brand's operator has run `claude /login` against distinct Anthropic accounts; if it's empty, both brands are still logged in to the same account (operator action, not a code bug).
|
|
122
122
|
3. `grep "\[install\] claude-creds pickup" ~/.${brand}/logs/install-*.log` — fires once on the first post-Task-923 install of any brand and moves the legacy `~/.claude/.credentials.json` into that brand's path. Subsequent brands install with no credentials and require a fresh `claude /login` inside that brand's chat (which writes to the brand-scoped path because the systemd unit env is in scope).
|
|
123
123
|
|
|
124
|
+
To deliberately disconnect the wrong account, use the header menu's **Disconnect Claude account** item (the supported path; no SSH or manual file deletion needed). It runs `claude auth logout`, then verifies the brand's `~/${brand.configDir}/.claude/.credentials.json` is actually gone before reporting success — a disconnect that left the credential in place reports failure in the menu rather than false-confirming. The server logs one line per attempt: `grep '\[onboarding\] op=claude-logout' ~/.${brand}/logs/server.log` — `credentialsPresent=true` after the call is a failed disconnect the operator may believe succeeded.
|
|
125
|
+
|
|
124
126
|
**All sessions on the brand stopped responding after a token expiry.** Symptom on the operator side: every spawn dies at `pid-file-timeout` and the dashboard health probe reports auth dead. Diagnose the OAuth refresh path before anything else:
|
|
125
127
|
|
|
126
128
|
1. `tail -n 300 ~/.${brand}/logs/server.log | grep -E 'auth-refresh|auth-health|invalid_grant'` — `op=lock-acquired` proves the cross-process lock is in play (Task 576). `op=skipped-fresh` means a sibling process (the admin server or a `claude` binary) already rotated the tokens during the lock wait — expected, healthy. `op=renewed expiresAt=…` is the only line that means a network refresh actually ran.
|
|
@@ -86,6 +86,10 @@ Observation is fully preserved: group messages are stored in the ring buffer, re
|
|
|
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 (Task 774)
|
|
90
|
+
|
|
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
|
+
|
|
89
93
|
## Live persistence
|
|
90
94
|
|
|
91
95
|
Every `messages.upsert` event (both `notify` and `append`, both `fromMe` directions) writes a `:Message:WhatsAppMessage` row to Neo4j attached to the sessionKey-keyed `:Conversation`. A single capture site at `platform/ui/app/lib/whatsapp/manager.ts` covers inbound, outbound (Baileys echoes agent-sent messages back through `messages.upsert` with `fromMe=true`), and owner-mirror — without touching `outbound/send.ts`. `messageId` namespace is `whatsapp-live:<waName>:<remoteJid>:<msg.key.id>` where `<waName>` is the Baileys credential dirname (e.g. `default`); distinct from the `:ConversationArchive` `:Section` chunks written by the source-agnostic `conversation-archive` skill (parent `:ConversationArchive` keyed on `conversationIdentity`, plus `:Section` children) — live and archive live in disjoint shapes. Persist failures are loud (`[whatsapp-persist] FAIL …`) and never block dispatch — silent loss is the worse failure mode.
|
|
@@ -150,9 +150,36 @@ 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 (Task 774)
|
|
154
|
+
|
|
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
|
+
|
|
157
|
+
The discriminator lives in `platform/ui/app/lib/whatsapp/inbound/self-chat.ts` (`evaluateSelfChatCommand`) and dispatches only when **all** of:
|
|
158
|
+
|
|
159
|
+
1. `msg.key.fromMe === true`;
|
|
160
|
+
2. `remoteJid` is the self surface — `selfJid`, `selfLid`, or `${selfPhone}@s.whatsapp.net`, with device suffixes (`447…:12@s.whatsapp.net`) normalised before comparison;
|
|
161
|
+
3. the message id is not in the agent-sent cache (`isAgentSentMessage`) — suppresses the agent's own self-chat reply echoes within the 2-minute TTL;
|
|
162
|
+
4. the message timestamp (Unix seconds, normalised to ms) is **strictly after** `conn.lastConnectedAt` — excludes history backfill replayed on reconnect, and is the restart-safe backstop when the in-memory echo cache has expired or been cleared;
|
|
163
|
+
5. it is not an in-session duplicate (`isDuplicateInbound`);
|
|
164
|
+
6. it has non-empty extracted text.
|
|
165
|
+
|
|
166
|
+
A match dispatches via `buildSelfChatDispatch` (`manager.ts`): `agentType: "admin"`, raw command text (no `[Owner reply]` prefix), `senderPhone = selfPhone`, the admin cacheKey (`whatsapp:<accountId>`), `reply`/`composing` closures bound to the self jid so the agent's answer returns to the self-chat, and **no `isOwnerMirror` flag** — the inbound route drops owner-mirror payloads before the gateway, and a self-chat message is a command expecting a reply, not a mirror. The reply goes out through `sendTextMessage`, which tracks the id for clause-3 echo suppression.
|
|
167
|
+
|
|
168
|
+
`fromMe` in any **non-self** thread is unchanged: it remains the `[Owner reply]` mirror described above, never a command.
|
|
169
|
+
|
|
170
|
+
**Log grammar** (one decision lineage per self-chat message, greppable by `msgId=`):
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
[whatsapp:self-chat] op=seen account=… msgId=… tsMs=… connAtMs=… fromMe=true
|
|
174
|
+
[whatsapp:self-chat] op=skip account=… msgId=… reason=echo|backfill|duplicate|no-text
|
|
175
|
+
[whatsapp:self-chat] op=dispatch account=… msgId=… bytes=… agentType=admin
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Failure signatures: an `op=dispatch` whose `tsMs ≤ connAtMs` (backfill wrongly dispatched — must never appear; the `op=seen` line carries both fields so this is checkable per message); back-to-back `op=dispatch` with no operator-originated message between (self-reply loop); a self-chat message the operator confirms sending with no `op=seen` at all (silent drop upstream of the discriminator).
|
|
179
|
+
|
|
153
180
|
## Offline Message Recovery
|
|
154
181
|
|
|
155
|
-
Baileys is a WhatsApp Web protocol client. When the gateway is offline, WhatsApp's servers hold incoming messages and push them back as history (`append`-type `messages.upsert` events) when Baileys reconnects — so the system self-recovers automatically on restart. These replayed messages are stored in the UI message cache and marked as read, but **not** dispatched to the agent (no auto-reply on catch-up). The recovery window is determined entirely by WhatsApp's server-side delivery queue (approximately 30 days); `syncFullHistory: false` only prevents requesting full chat history on first pairing and does not restrict this window.
|
|
182
|
+
Baileys is a WhatsApp Web protocol client. When the gateway is offline, WhatsApp's servers hold incoming messages and push them back as history (`append`-type `messages.upsert` events) when Baileys reconnects — so the system self-recovers automatically on restart. These replayed messages are stored in the UI message cache and marked as read, but **not** dispatched to the agent (no auto-reply on catch-up). The self-chat command lane (above) does not weaken this: replayed history fails its strictly-after-`lastConnectedAt` timestamp clause and skips with `reason=backfill`. The recovery window is determined entirely by WhatsApp's server-side delivery queue (approximately 30 days); `syncFullHistory: false` only prevents requesting full chat history on first pairing and does not restrict this window.
|
|
156
183
|
|
|
157
184
|
## Voice Notes
|
|
158
185
|
|
|
@@ -5,6 +5,7 @@ import type { RateLimiter } from './spawn-rate-limiter.js';
|
|
|
5
5
|
import type { AuditRegistry } from './public-tool-audit.js';
|
|
6
6
|
import { type UserTitleStore } from './user-title-store.js';
|
|
7
7
|
import { WaChannelStore } from './wa-channel-store.js';
|
|
8
|
+
import { WebchatChannelStore } from './webchat-channel-store.js';
|
|
8
9
|
export declare const SESSION_ID_PATTERN: RegExp;
|
|
9
10
|
export declare const RC_SPAWN_URL_WAIT_DEFAULT_MS = 60000;
|
|
10
11
|
export interface HttpDeps extends Omit<SpawnDeps, 'onSessionReady' | 'watcher'> {
|
|
@@ -24,6 +25,11 @@ export interface HttpDeps extends Omit<SpawnDeps, 'onSessionReady' | 'watcher'>
|
|
|
24
25
|
* write-throughs it; every resume path reads it back to re-apply the
|
|
25
26
|
* channel argv so `reply` is present on every live PTY for the session. */
|
|
26
27
|
waChannelStore: WaChannelStore;
|
|
28
|
+
/** Task 772 — per-session webchat channel binding, the webchat twin of
|
|
29
|
+
* waChannelStore. Optional so the pre-772 test deps factories (which run
|
|
30
|
+
* the live /rc-spawn handler) keep working unchanged; production index.ts
|
|
31
|
+
* always injects it. */
|
|
32
|
+
webchatChannelStore?: WebchatChannelStore;
|
|
27
33
|
/** Task 648 — override the `/rc-spawn` pid/bind poll bound. Production
|
|
28
34
|
* omits this and gets RC_SPAWN_URL_WAIT_DEFAULT_MS (60s); tests inject a
|
|
29
35
|
* short value to exercise the bind-timeout response without a 60s wait. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAU3B,OAAO,EAgBL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAoBzB,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAgFhE,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAS1D,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAIlD,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E;;qEAEiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,SAAS,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,WAAW,CAAA;IAC/B,eAAe,EAAE,aAAa,CAAA;IAC9B;kFAC8E;IAC9E,cAAc,EAAE,cAAc,CAAA;IAC9B;;gFAE4E;IAC5E,cAAc,EAAE,cAAc,CAAA;IAC9B;;;6BAGyB;IACzB,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;;gFAE4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AA+MD;;;kEAGkE;AAClE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpD;;;+CAG2C;IAC3C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD,CAAA;AAmBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAu8DpD"}
|
|
@@ -24,6 +24,7 @@ import { stream } from 'hono/streaming';
|
|
|
24
24
|
import { existsSync, statSync, createReadStream, watchFile, unwatchFile, rmSync, mkdirSync, renameSync, readFileSync, writeFileSync } from 'node:fs';
|
|
25
25
|
import { tmpdir } from 'node:os';
|
|
26
26
|
import { buildWaChannelMcpServers, buildWaChannelArgv, waChannelMcpConfigPath, isDevChannelPrompt } from './wa-channel-mcp.js';
|
|
27
|
+
import { buildWebchatChannelMcpServers, buildWebchatChannelArgv, webchatChannelMcpConfigPath } from './webchat-channel-mcp.js';
|
|
27
28
|
import { randomUUID } from 'node:crypto';
|
|
28
29
|
import { execFileSync } from 'node:child_process';
|
|
29
30
|
import { buildRcChildEnv } from './rc-daemon.js';
|
|
@@ -1305,6 +1306,7 @@ export function buildHttpApp(deps) {
|
|
|
1305
1306
|
// doesn't accrete orphans and a recreated session can't inherit a stale
|
|
1306
1307
|
// binding. Best-effort; the store logs its own failure.
|
|
1307
1308
|
deps.waChannelStore.remove(sessionId);
|
|
1309
|
+
deps.webchatChannelStore?.remove(sessionId);
|
|
1308
1310
|
// Task 702 — the transcript is gone; its reattach hint must go too.
|
|
1309
1311
|
removeScopeRecord(sessionsDir, sessionId, deps.logger);
|
|
1310
1312
|
deps.logger(`purge sessionId=${sessionId.slice(0, 8)} bytes=${bytes}`);
|
|
@@ -1629,6 +1631,18 @@ export function buildHttpApp(deps) {
|
|
|
1629
1631
|
typeof waChannelRaw.serverPath === 'string'
|
|
1630
1632
|
? waChannelRaw
|
|
1631
1633
|
: undefined;
|
|
1634
|
+
// Task 772 — optional webchat native-channel attach, the webchat twin of
|
|
1635
|
+
// waChannel. All three fields must be present strings or the attach is
|
|
1636
|
+
// skipped. A session is whatsapp XOR webchat, so at most one of the two
|
|
1637
|
+
// channel blocks below fires.
|
|
1638
|
+
const webchatChannelRaw = body.webchatChannel;
|
|
1639
|
+
const webchatChannel = webchatChannelRaw !== null &&
|
|
1640
|
+
typeof webchatChannelRaw === 'object' &&
|
|
1641
|
+
typeof webchatChannelRaw.key === 'string' &&
|
|
1642
|
+
typeof webchatChannelRaw.gatewayUrl === 'string' &&
|
|
1643
|
+
typeof webchatChannelRaw.serverPath === 'string'
|
|
1644
|
+
? webchatChannelRaw
|
|
1645
|
+
: undefined;
|
|
1632
1646
|
// Task 724 — the bridge routes EVERY role=admin channel inbound here, not
|
|
1633
1647
|
// just WhatsApp, and supplies the real channel (+ role:'admin', senderId) so
|
|
1634
1648
|
// the classification sidecar below records the actual channel. The WaGateway
|
|
@@ -1740,6 +1754,49 @@ export function buildHttpApp(deps) {
|
|
|
1740
1754
|
deps.logger(`[rc-spawn] op=wa-channel-write-failed err=${JSON.stringify(err instanceof Error ? err.message : String(err))}`);
|
|
1741
1755
|
}
|
|
1742
1756
|
}
|
|
1757
|
+
// Task 772 — webchat native channel, the webchat twin of the waChannel
|
|
1758
|
+
// block above. Same contract: a gateway spawn carries `webchatChannel` and
|
|
1759
|
+
// write-throughs the binding; a bare resume reads it back, so
|
|
1760
|
+
// `mcp__webchat-channel__reply` is present on every live PTY for the
|
|
1761
|
+
// session. A session is whatsapp XOR webchat — enforced here: when a
|
|
1762
|
+
// WhatsApp binding is already effective for this session, the webchat
|
|
1763
|
+
// attach is refused (two channel servers on one PTY is never valid).
|
|
1764
|
+
let effectiveWebchatChannel;
|
|
1765
|
+
if (sessionId && effectiveWaChannel && (webchatChannel || deps.webchatChannelStore?.get(sessionId))) {
|
|
1766
|
+
deps.logger(`[rc-spawn] op=webchat-channel-refused sessionId=${sessionId.slice(0, 8)} reason=wa-channel-already-bound`);
|
|
1767
|
+
}
|
|
1768
|
+
else if (sessionId) {
|
|
1769
|
+
if (webchatChannel) {
|
|
1770
|
+
effectiveWebchatChannel = webchatChannel;
|
|
1771
|
+
deps.webchatChannelStore?.set(sessionId, webchatChannel);
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
const stored = deps.webchatChannelStore?.get(sessionId);
|
|
1775
|
+
if (stored) {
|
|
1776
|
+
effectiveWebchatChannel = { key: stored.key, gatewayUrl: stored.gatewayUrl, serverPath: stored.serverPath };
|
|
1777
|
+
deps.logger(`[rc-spawn] op=webchat-channel-rebind sessionId=${sessionId.slice(0, 8)} key=${stored.key} source=store`);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
let webchatPath = null;
|
|
1782
|
+
if (effectiveWebchatChannel && sessionId) {
|
|
1783
|
+
const webchatServers = buildWebchatChannelMcpServers({
|
|
1784
|
+
sessionId,
|
|
1785
|
+
key: effectiveWebchatChannel.key,
|
|
1786
|
+
gatewayUrl: effectiveWebchatChannel.gatewayUrl,
|
|
1787
|
+
serverPath: effectiveWebchatChannel.serverPath,
|
|
1788
|
+
});
|
|
1789
|
+
webchatPath = webchatChannelMcpConfigPath(sessionId, tmpdir());
|
|
1790
|
+
try {
|
|
1791
|
+
writeFileSync(webchatPath, JSON.stringify({ mcpServers: webchatServers }, null, 2), 'utf8');
|
|
1792
|
+
argv.push(...buildWebchatChannelArgv(webchatPath));
|
|
1793
|
+
deps.logger(`[rc-spawn] op=webchat-channel key=${effectiveWebchatChannel.key} gateway=${effectiveWebchatChannel.gatewayUrl} path=${webchatPath}`);
|
|
1794
|
+
}
|
|
1795
|
+
catch (err) {
|
|
1796
|
+
webchatPath = null;
|
|
1797
|
+
deps.logger(`[rc-spawn] op=webchat-channel-write-failed err=${JSON.stringify(err instanceof Error ? err.message : String(err))}`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1743
1800
|
// Task 626 — the initial message is the trailing positional, processed as
|
|
1744
1801
|
// the session's first user turn at PTY startup. One-shot jobs omit `name`
|
|
1745
1802
|
// so the prompt is the sole positional.
|
|
@@ -1828,6 +1885,10 @@ export function buildHttpApp(deps) {
|
|
|
1828
1885
|
if (sessionId && deps.waChannelStore.get(sessionId) && waPath === null) {
|
|
1829
1886
|
deps.logger(`[rc-spawn] op=channel-binding-lost sessionId=${sessionId} pid=${pty.pid} reason=resume-without-mcp-config`);
|
|
1830
1887
|
}
|
|
1888
|
+
// Task 772 — the same post-condition for the webchat binding.
|
|
1889
|
+
if (sessionId && deps.webchatChannelStore?.get(sessionId) && webchatPath === null) {
|
|
1890
|
+
deps.logger(`[rc-spawn] op=webchat-channel-binding-lost sessionId=${sessionId} pid=${pty.pid} reason=resume-without-mcp-config`);
|
|
1891
|
+
}
|
|
1831
1892
|
// Tracker registration — closes the leak gap the Task 552 path opened.
|
|
1832
1893
|
// For fresh spawns without a caller-supplied sessionId we register the
|
|
1833
1894
|
// tracker under the unitToken so the entry has a stable key; the
|
|
@@ -1863,7 +1924,7 @@ export function buildHttpApp(deps) {
|
|
|
1863
1924
|
// a failed classification sidecar must not tear it down, so it logs and the
|
|
1864
1925
|
// session keeps running (the pre-fix reader-blind behaviour, no worse).
|
|
1865
1926
|
const classifyChannel = adminRole === 'admin' ? adminChannel : undefined;
|
|
1866
|
-
const classifySenderId = adminSenderId ?? effectiveWaChannel?.senderId;
|
|
1927
|
+
const classifySenderId = adminSenderId ?? effectiveWaChannel?.senderId ?? effectiveWebchatChannel?.key;
|
|
1867
1928
|
if (sessionId && classifyChannel && classifySenderId) {
|
|
1868
1929
|
const classifySidecarPath = sidecarPathForSessionId(deps.spawnCwd, sessionId);
|
|
1869
1930
|
if (!existsSync(classifySidecarPath)) {
|
|
@@ -1935,7 +1996,8 @@ export function buildHttpApp(deps) {
|
|
|
1935
1996
|
// no literal spaces, so match on the ANSI/space-stripped letter stream.
|
|
1936
1997
|
// Bounded startup keystroke only — inbound message submission stays a
|
|
1937
1998
|
// channel event (the whole point of the migration). Armed only when a
|
|
1938
|
-
// waChannel
|
|
1999
|
+
// native channel (waChannel or webchatChannel, Task 772) is attached,
|
|
2000
|
+
// fired at most once — the prompt text is channel-agnostic.
|
|
1939
2001
|
let devChannelConfirmed = false;
|
|
1940
2002
|
let devChannelScan = '';
|
|
1941
2003
|
let resolveStdoutSlug = () => { };
|
|
@@ -1943,7 +2005,7 @@ export function buildHttpApp(deps) {
|
|
|
1943
2005
|
const BIND_BUF_TAIL = 8 * 1024;
|
|
1944
2006
|
pty.onData((data) => {
|
|
1945
2007
|
stdoutBytes += data.length;
|
|
1946
|
-
if (effectiveWaChannel && !devChannelConfirmed) {
|
|
2008
|
+
if ((effectiveWaChannel || effectiveWebchatChannel) && !devChannelConfirmed) {
|
|
1947
2009
|
devChannelScan += data;
|
|
1948
2010
|
if (devChannelScan.length > 4096)
|
|
1949
2011
|
devChannelScan = devChannelScan.slice(devChannelScan.length - 4096);
|