@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/payload/server/server.js
CHANGED
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
vncLog,
|
|
98
98
|
walkPremiumBundles,
|
|
99
99
|
writeAdminUserAndPerson
|
|
100
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-QDCBAHPI.js";
|
|
101
101
|
import {
|
|
102
102
|
__commonJS,
|
|
103
103
|
__toESM
|
|
@@ -411,6 +411,18 @@ var require_dist2 = __commonJS({
|
|
|
411
411
|
Property: "#5C7A99",
|
|
412
412
|
Viewing: "#D4A574",
|
|
413
413
|
Offer: "#B08850",
|
|
414
|
+
// Construction ontology (Task 773; labels registered in schema.cypher by
|
|
415
|
+
// Task 650). Muted brick-red family — the construction register sits
|
|
416
|
+
// apart from people-terracotta, viewing-amber, and task-rose bands so
|
|
417
|
+
// siteoffice legends read as their own group. Top-level (operator-entry)
|
|
418
|
+
// labels only; child labels (LineItem, Milestone, Valuation,
|
|
419
|
+
// VariationNote, TimeLog) intentionally stay fallback-grey.
|
|
420
|
+
Job: "#8C3B2E",
|
|
421
|
+
QuoteDocument: "#A65A4A",
|
|
422
|
+
InboundInvoice: "#6B2A20",
|
|
423
|
+
SubContractor: "#BF7E6E",
|
|
424
|
+
SubInvoice: "#52201A",
|
|
425
|
+
WhatsAppGroup: "#D4A096",
|
|
414
426
|
// External business entity — schema:Organization, written by memory-write
|
|
415
427
|
// as the Supplier label. Warm tobacco — distinct hue from internal-business
|
|
416
428
|
// slates so external entities are scannable at a glance.
|
|
@@ -1203,7 +1215,7 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1203
1215
|
|
|
1204
1216
|
// server/index.ts
|
|
1205
1217
|
import { readFileSync as readFileSync24, existsSync as existsSync25, watchFile } from "fs";
|
|
1206
|
-
import { resolve as resolve27, join as
|
|
1218
|
+
import { resolve as resolve27, join as join20, basename as basename8 } from "path";
|
|
1207
1219
|
import { homedir as homedir3 } from "os";
|
|
1208
1220
|
import { monitorEventLoopDelay } from "perf_hooks";
|
|
1209
1221
|
|
|
@@ -2432,8 +2444,45 @@ function isDuplicateInbound(key) {
|
|
|
2432
2444
|
return true;
|
|
2433
2445
|
}
|
|
2434
2446
|
|
|
2447
|
+
// app/lib/whatsapp/inbound/self-chat.ts
|
|
2448
|
+
var TAG4 = "[whatsapp:self-chat]";
|
|
2449
|
+
function normalizeJid(jid) {
|
|
2450
|
+
const m = jid.match(/^(\d+)(?::\d+)?@(s\.whatsapp\.net|lid)$/i);
|
|
2451
|
+
return m ? `${m[1]}@${m[2].toLowerCase()}` : jid;
|
|
2452
|
+
}
|
|
2453
|
+
function isSelfChatJid(remoteJid, self) {
|
|
2454
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
2455
|
+
if (self.selfJid) candidates.add(normalizeJid(self.selfJid));
|
|
2456
|
+
if (self.selfLid) candidates.add(normalizeJid(self.selfLid));
|
|
2457
|
+
if (self.selfPhone) candidates.add(`${self.selfPhone}@s.whatsapp.net`);
|
|
2458
|
+
if (candidates.size === 0) return false;
|
|
2459
|
+
return candidates.has(normalizeJid(remoteJid));
|
|
2460
|
+
}
|
|
2461
|
+
function evaluateSelfChatCommand(input) {
|
|
2462
|
+
if (!input.fromMe) return { kind: "not-self-chat" };
|
|
2463
|
+
if (!isSelfChatJid(input.remoteJid, input)) return { kind: "not-self-chat" };
|
|
2464
|
+
const tsMs = input.messageTimestampSec ? input.messageTimestampSec * 1e3 : 0;
|
|
2465
|
+
const connAtMs = input.lastConnectedAtMs ?? 0;
|
|
2466
|
+
const msgId = input.msgId ?? "?";
|
|
2467
|
+
console.error(
|
|
2468
|
+
`${TAG4} op=seen account=${input.accountId} msgId=${msgId} tsMs=${tsMs} connAtMs=${connAtMs} fromMe=true`
|
|
2469
|
+
);
|
|
2470
|
+
const skip = (reason) => {
|
|
2471
|
+
console.error(`${TAG4} op=skip account=${input.accountId} msgId=${msgId} reason=${reason}`);
|
|
2472
|
+
return { kind: "skip", reason };
|
|
2473
|
+
};
|
|
2474
|
+
if (input.msgId && isAgentSentMessage(input.msgId)) return skip("echo");
|
|
2475
|
+
if (!(tsMs > connAtMs)) return skip("backfill");
|
|
2476
|
+
if (isDuplicateInbound(`${input.accountId}:${input.remoteJid}:${input.msgId}`)) return skip("duplicate");
|
|
2477
|
+
if (!input.text) return skip("no-text");
|
|
2478
|
+
console.error(
|
|
2479
|
+
`${TAG4} op=dispatch account=${input.accountId} msgId=${msgId} bytes=${Buffer.byteLength(input.text, "utf8")} agentType=admin`
|
|
2480
|
+
);
|
|
2481
|
+
return { kind: "dispatch", text: input.text };
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2435
2484
|
// app/lib/whatsapp/activity.ts
|
|
2436
|
-
var
|
|
2485
|
+
var TAG5 = "[whatsapp:activity]";
|
|
2437
2486
|
var RECENT_EVENTS_MAX = 200;
|
|
2438
2487
|
var counters = /* @__PURE__ */ new Map();
|
|
2439
2488
|
var recentEvents = [];
|
|
@@ -2461,10 +2510,10 @@ function recordActivity(event) {
|
|
|
2461
2510
|
recentEvents.splice(0, recentEvents.length - RECENT_EVENTS_MAX);
|
|
2462
2511
|
}
|
|
2463
2512
|
console.error(
|
|
2464
|
-
`${
|
|
2513
|
+
`${TAG5} channel-activity: direction=${direction} account=${accountId} jid=${jid}` + (messageType ? ` type=${messageType}` : "")
|
|
2465
2514
|
);
|
|
2466
2515
|
} catch (err) {
|
|
2467
|
-
console.error(`${
|
|
2516
|
+
console.error(`${TAG5} recording failed: ${String(err)}`);
|
|
2468
2517
|
}
|
|
2469
2518
|
}
|
|
2470
2519
|
function getChannelActivity(accountId) {
|
|
@@ -2485,7 +2534,7 @@ function getChannelActivity(accountId) {
|
|
|
2485
2534
|
}
|
|
2486
2535
|
|
|
2487
2536
|
// app/lib/whatsapp/outbound/send.ts
|
|
2488
|
-
var
|
|
2537
|
+
var TAG6 = "[whatsapp:outbound]";
|
|
2489
2538
|
async function sendTextMessage(sock, to, text, opts) {
|
|
2490
2539
|
try {
|
|
2491
2540
|
const jid = to.includes("@") ? to : toWhatsappJid(to);
|
|
@@ -2497,14 +2546,14 @@ async function sendTextMessage(sock, to, text, opts) {
|
|
|
2497
2546
|
const messageId = result?.key?.id;
|
|
2498
2547
|
if (messageId) {
|
|
2499
2548
|
trackAgentSentMessage(messageId);
|
|
2500
|
-
console.error(`${
|
|
2549
|
+
console.error(`${TAG6} sent text to=${jid} id=${messageId}`);
|
|
2501
2550
|
}
|
|
2502
2551
|
if (opts?.accountId) {
|
|
2503
2552
|
recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: "text" });
|
|
2504
2553
|
}
|
|
2505
2554
|
return { success: true, messageId: messageId ?? void 0 };
|
|
2506
2555
|
} catch (err) {
|
|
2507
|
-
console.error(`${
|
|
2556
|
+
console.error(`${TAG6} send failed to=${to}: ${String(err)}`);
|
|
2508
2557
|
return { success: false, error: String(err) };
|
|
2509
2558
|
}
|
|
2510
2559
|
}
|
|
@@ -2573,20 +2622,20 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
2573
2622
|
const messageId = result?.key?.id;
|
|
2574
2623
|
if (messageId) {
|
|
2575
2624
|
trackAgentSentMessage(messageId);
|
|
2576
|
-
console.error(`${
|
|
2625
|
+
console.error(`${TAG6} sent ${media.type} to=${jid} id=${messageId}`);
|
|
2577
2626
|
}
|
|
2578
2627
|
if (opts?.accountId) {
|
|
2579
2628
|
recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: media.type });
|
|
2580
2629
|
}
|
|
2581
2630
|
return { success: true, messageId: messageId ?? void 0 };
|
|
2582
2631
|
} catch (err) {
|
|
2583
|
-
console.error(`${
|
|
2632
|
+
console.error(`${TAG6} send media failed to=${to}: ${String(err)}`);
|
|
2584
2633
|
return { success: false, error: String(err) };
|
|
2585
2634
|
}
|
|
2586
2635
|
}
|
|
2587
2636
|
|
|
2588
2637
|
// app/lib/whatsapp/persist-message.ts
|
|
2589
|
-
var
|
|
2638
|
+
var TAG7 = "[whatsapp-persist]";
|
|
2590
2639
|
var sessionWriteLocks = /* @__PURE__ */ new Map();
|
|
2591
2640
|
async function persistWhatsAppMessage(input) {
|
|
2592
2641
|
if (!input.body || !input.msgKeyId) return null;
|
|
@@ -2691,7 +2740,7 @@ async function persistWhatsAppMessage(input) {
|
|
|
2691
2740
|
const result = await session.run(cypher, params);
|
|
2692
2741
|
const ms = Date.now() - t0;
|
|
2693
2742
|
if (result.records.length === 0) {
|
|
2694
|
-
console.error(`${
|
|
2743
|
+
console.error(`${TAG7} skip reason=conversation-not-found accountId=${input.platformAccountId} cacheKey=${input.cacheKey} messageId=${messageId}`);
|
|
2695
2744
|
return null;
|
|
2696
2745
|
}
|
|
2697
2746
|
const rec = result.records[0];
|
|
@@ -2700,26 +2749,26 @@ async function persistWhatsAppMessage(input) {
|
|
|
2700
2749
|
const senderElementId = rec.get("senderElementId");
|
|
2701
2750
|
const senderReused = rec.get("senderReused") === true;
|
|
2702
2751
|
if (existed) {
|
|
2703
|
-
console.error(`${
|
|
2752
|
+
console.error(`${TAG7} noop reason=existing-message messageId=${messageId}`);
|
|
2704
2753
|
return { messageId, created: false, senderElementId };
|
|
2705
2754
|
}
|
|
2706
2755
|
console.error(
|
|
2707
|
-
`${
|
|
2756
|
+
`${TAG7} sender-resolved telephone=${senderTelephone} nodeKind=Person elementId=${senderElementId} reused=${senderReused}`
|
|
2708
2757
|
);
|
|
2709
2758
|
if (prevMessageId) {
|
|
2710
|
-
console.error(`${
|
|
2759
|
+
console.error(`${TAG7} next-merge prior=${prevMessageId} next=${messageId}`);
|
|
2711
2760
|
} else {
|
|
2712
|
-
console.error(`${
|
|
2761
|
+
console.error(`${TAG7} next-skip reason=no-prior msgId=${messageId}`);
|
|
2713
2762
|
}
|
|
2714
2763
|
console.error(
|
|
2715
|
-
`${
|
|
2764
|
+
`${TAG7} write messageId=${messageId} accountId=${input.platformAccountId} cacheKey=${input.cacheKey} fromMe=${input.fromMe} dateSent=${dateSentIso} bodyBytes=${Buffer.byteLength(input.body, "utf8")} ms=${ms}`
|
|
2716
2765
|
);
|
|
2717
2766
|
return { messageId, created: true, senderElementId };
|
|
2718
2767
|
} catch (err) {
|
|
2719
2768
|
const ms = Date.now() - t0;
|
|
2720
2769
|
const reason = sanitizeReason(err);
|
|
2721
2770
|
console.error(
|
|
2722
|
-
`${
|
|
2771
|
+
`${TAG7} FAIL accountId=${input.platformAccountId} waname=${input.accountId} remoteJid=${input.remoteJid} msgKey=${input.msgKeyId} reason=${reason} ms=${ms}`
|
|
2723
2772
|
);
|
|
2724
2773
|
return null;
|
|
2725
2774
|
} finally {
|
|
@@ -2752,7 +2801,7 @@ function sanitizeReason(err) {
|
|
|
2752
2801
|
}
|
|
2753
2802
|
|
|
2754
2803
|
// app/lib/whatsapp/ensure-conversation.ts
|
|
2755
|
-
var
|
|
2804
|
+
var TAG8 = "[whatsapp-persist]";
|
|
2756
2805
|
async function ensureWhatsAppConversation(input) {
|
|
2757
2806
|
const t0 = Date.now();
|
|
2758
2807
|
try {
|
|
@@ -2764,19 +2813,19 @@ async function ensureWhatsAppConversation(input) {
|
|
|
2764
2813
|
const ms = Date.now() - t0;
|
|
2765
2814
|
if (!result.sessionId) {
|
|
2766
2815
|
console.error(
|
|
2767
|
-
`${
|
|
2816
|
+
`${TAG8} conversation-merged FAIL cacheKey=${input.cacheKey} reason=null-sessionId ms=${ms}`
|
|
2768
2817
|
);
|
|
2769
2818
|
return null;
|
|
2770
2819
|
}
|
|
2771
2820
|
console.error(
|
|
2772
|
-
`${
|
|
2821
|
+
`${TAG8} conversation-merged cacheKey=${input.cacheKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
|
|
2773
2822
|
);
|
|
2774
2823
|
return { sessionId: result.sessionId, created: result.created };
|
|
2775
2824
|
} catch (err) {
|
|
2776
2825
|
const ms = Date.now() - t0;
|
|
2777
2826
|
const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
|
|
2778
2827
|
console.error(
|
|
2779
|
-
`${
|
|
2828
|
+
`${TAG8} conversation-merged FAIL cacheKey=${input.cacheKey} reason=${reason} ms=${ms}`
|
|
2780
2829
|
);
|
|
2781
2830
|
return null;
|
|
2782
2831
|
}
|
|
@@ -2867,7 +2916,7 @@ import {
|
|
|
2867
2916
|
downloadContentFromMessage,
|
|
2868
2917
|
normalizeMessageContent as normalizeMessageContent2
|
|
2869
2918
|
} from "@whiskeysockets/baileys";
|
|
2870
|
-
var
|
|
2919
|
+
var TAG9 = "[whatsapp:media]";
|
|
2871
2920
|
var MEDIA_DIR = "/tmp/maxy-media";
|
|
2872
2921
|
function mimeToExt(mimetype) {
|
|
2873
2922
|
const map = {
|
|
@@ -2923,25 +2972,25 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
2923
2972
|
}
|
|
2924
2973
|
);
|
|
2925
2974
|
if (!buffer || buffer.length === 0) {
|
|
2926
|
-
console.error(`${
|
|
2975
|
+
console.error(`${TAG9} primary download returned empty, trying direct fallback`);
|
|
2927
2976
|
const downloadable = getDownloadableContent(content);
|
|
2928
2977
|
if (downloadable) {
|
|
2929
2978
|
try {
|
|
2930
2979
|
const stream2 = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
2931
2980
|
buffer = await streamToBuffer(stream2);
|
|
2932
2981
|
} catch (fallbackErr) {
|
|
2933
|
-
console.error(`${
|
|
2982
|
+
console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
|
|
2934
2983
|
}
|
|
2935
2984
|
}
|
|
2936
2985
|
}
|
|
2937
2986
|
if (!buffer || buffer.length === 0) {
|
|
2938
|
-
console.error(`${
|
|
2987
|
+
console.error(`${TAG9} download failed: empty buffer for ${mimetype ?? "unknown"}`);
|
|
2939
2988
|
return void 0;
|
|
2940
2989
|
}
|
|
2941
2990
|
if (buffer.length > maxBytes) {
|
|
2942
2991
|
const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
|
|
2943
2992
|
const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
|
|
2944
|
-
console.error(`${
|
|
2993
|
+
console.error(`${TAG9} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
|
|
2945
2994
|
return void 0;
|
|
2946
2995
|
}
|
|
2947
2996
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
@@ -2950,20 +2999,20 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
2950
2999
|
const filePath = join2(MEDIA_DIR, filename);
|
|
2951
3000
|
await writeFile(filePath, buffer);
|
|
2952
3001
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
2953
|
-
console.error(`${
|
|
3002
|
+
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
2954
3003
|
return {
|
|
2955
3004
|
path: filePath,
|
|
2956
3005
|
mimetype: mimetype ?? "application/octet-stream",
|
|
2957
3006
|
size: buffer.length
|
|
2958
3007
|
};
|
|
2959
3008
|
} catch (err) {
|
|
2960
|
-
console.error(`${
|
|
3009
|
+
console.error(`${TAG9} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
|
|
2961
3010
|
return void 0;
|
|
2962
3011
|
}
|
|
2963
3012
|
}
|
|
2964
3013
|
|
|
2965
3014
|
// app/lib/whatsapp/inbound/debounce.ts
|
|
2966
|
-
var
|
|
3015
|
+
var TAG10 = "[whatsapp:debounce]";
|
|
2967
3016
|
var STT_TAG = "[whatsapp:stt-await]";
|
|
2968
3017
|
function createInboundDebouncer(opts) {
|
|
2969
3018
|
const { debounceMs, buildKey, onFlush, onError } = opts;
|
|
@@ -2994,7 +3043,7 @@ function createInboundDebouncer(opts) {
|
|
|
2994
3043
|
pending.delete(key);
|
|
2995
3044
|
const batchSize = batch.entries.length;
|
|
2996
3045
|
try {
|
|
2997
|
-
console.error(`${
|
|
3046
|
+
console.error(`${TAG10} debounce flush key=${key} batchSize=${batchSize}`);
|
|
2998
3047
|
const result = onFlush(batch.entries);
|
|
2999
3048
|
if (result && typeof result.catch === "function") {
|
|
3000
3049
|
result.catch(onError);
|
|
@@ -3074,7 +3123,7 @@ function combineInboundBatch(entries) {
|
|
|
3074
3123
|
}
|
|
3075
3124
|
|
|
3076
3125
|
// app/lib/whatsapp/opening-hours.ts
|
|
3077
|
-
var
|
|
3126
|
+
var TAG11 = "[whatsapp:hours]";
|
|
3078
3127
|
async function isBusinessOpen(accountId) {
|
|
3079
3128
|
try {
|
|
3080
3129
|
const timezone = await resolveTimezone(accountId);
|
|
@@ -3092,7 +3141,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3092
3141
|
{ accountId, dayOfWeek, previousDayOfWeek }
|
|
3093
3142
|
);
|
|
3094
3143
|
if (result.records.length === 0) {
|
|
3095
|
-
console.error(`${
|
|
3144
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3096
3145
|
return { open: true, reason: "no opening hours configured" };
|
|
3097
3146
|
}
|
|
3098
3147
|
const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
|
|
@@ -3101,7 +3150,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3101
3150
|
closes: String(r.get("closes")).trim()
|
|
3102
3151
|
}));
|
|
3103
3152
|
if (specs.length === 0) {
|
|
3104
|
-
console.error(`${
|
|
3153
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3105
3154
|
return { open: true, reason: "no opening hours configured" };
|
|
3106
3155
|
}
|
|
3107
3156
|
for (const spec of specs) {
|
|
@@ -3111,7 +3160,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3111
3160
|
if (spec.opens > spec.closes && currentTime < spec.closes) {
|
|
3112
3161
|
const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
|
|
3113
3162
|
console.error(
|
|
3114
|
-
`${
|
|
3163
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
3115
3164
|
);
|
|
3116
3165
|
return {
|
|
3117
3166
|
open: true,
|
|
@@ -3124,7 +3173,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3124
3173
|
} else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
|
|
3125
3174
|
const hoursStr = `${spec.opens}-${spec.closes}`;
|
|
3126
3175
|
console.error(
|
|
3127
|
-
`${
|
|
3176
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
3128
3177
|
);
|
|
3129
3178
|
return {
|
|
3130
3179
|
open: true,
|
|
@@ -3138,7 +3187,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3138
3187
|
const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
|
|
3139
3188
|
const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
|
|
3140
3189
|
console.error(
|
|
3141
|
-
`${
|
|
3190
|
+
`${TAG11} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
|
|
3142
3191
|
);
|
|
3143
3192
|
return {
|
|
3144
3193
|
open: false,
|
|
@@ -3152,7 +3201,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3152
3201
|
}
|
|
3153
3202
|
} catch (err) {
|
|
3154
3203
|
console.error(
|
|
3155
|
-
`${
|
|
3204
|
+
`${TAG11} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
|
|
3156
3205
|
);
|
|
3157
3206
|
return { open: true, reason: "hours check failed (treating as open)" };
|
|
3158
3207
|
}
|
|
@@ -3198,7 +3247,7 @@ import { execFile } from "child_process";
|
|
|
3198
3247
|
import { unlink, stat } from "fs/promises";
|
|
3199
3248
|
import { promisify } from "util";
|
|
3200
3249
|
var execFileAsync = promisify(execFile);
|
|
3201
|
-
var
|
|
3250
|
+
var TAG12 = "[stt]";
|
|
3202
3251
|
var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
|
|
3203
3252
|
var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
|
|
3204
3253
|
var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
@@ -3209,11 +3258,11 @@ async function transcribe(audioPath, mimetype) {
|
|
|
3209
3258
|
const s = await stat(audioPath);
|
|
3210
3259
|
audioBytes = s.size;
|
|
3211
3260
|
} catch {
|
|
3212
|
-
console.error(`${
|
|
3261
|
+
console.error(`${TAG12} failed: file not readable path=${audioPath}`);
|
|
3213
3262
|
return void 0;
|
|
3214
3263
|
}
|
|
3215
3264
|
console.error(
|
|
3216
|
-
`${
|
|
3265
|
+
`${TAG12} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
|
|
3217
3266
|
);
|
|
3218
3267
|
const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
|
|
3219
3268
|
try {
|
|
@@ -3232,7 +3281,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
3232
3281
|
], { timeout: 3e4 });
|
|
3233
3282
|
} catch (err) {
|
|
3234
3283
|
const reason = err instanceof Error ? err.message : String(err);
|
|
3235
|
-
console.error(`${
|
|
3284
|
+
console.error(`${TAG12} failed: ffmpeg conversion error=${reason}`);
|
|
3236
3285
|
return void 0;
|
|
3237
3286
|
}
|
|
3238
3287
|
try {
|
|
@@ -3250,20 +3299,20 @@ async function transcribe(audioPath, mimetype) {
|
|
|
3250
3299
|
const text = stdout.trim();
|
|
3251
3300
|
const durationMs = Date.now() - startMs;
|
|
3252
3301
|
if (!text) {
|
|
3253
|
-
console.error(`${
|
|
3302
|
+
console.error(`${TAG12} failed: whisper returned empty output duration_ms=${durationMs}`);
|
|
3254
3303
|
return void 0;
|
|
3255
3304
|
}
|
|
3256
3305
|
const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
|
|
3257
3306
|
const language = langMatch?.[1] ?? "unknown";
|
|
3258
3307
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
3259
3308
|
console.error(
|
|
3260
|
-
`${
|
|
3309
|
+
`${TAG12} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
|
|
3261
3310
|
);
|
|
3262
3311
|
return { text, language, durationMs };
|
|
3263
3312
|
} catch (err) {
|
|
3264
3313
|
const durationMs = Date.now() - startMs;
|
|
3265
3314
|
const reason = err instanceof Error ? err.message : String(err);
|
|
3266
|
-
console.error(`${
|
|
3315
|
+
console.error(`${TAG12} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
|
|
3267
3316
|
return void 0;
|
|
3268
3317
|
} finally {
|
|
3269
3318
|
unlink(wavPath).catch(() => {
|
|
@@ -3282,7 +3331,7 @@ var WaReplyError = class extends Error {
|
|
|
3282
3331
|
};
|
|
3283
3332
|
|
|
3284
3333
|
// app/lib/whatsapp/manager.ts
|
|
3285
|
-
var
|
|
3334
|
+
var TAG13 = "[whatsapp:manager]";
|
|
3286
3335
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
3287
3336
|
var MESSAGE_STORE_MAX = 500;
|
|
3288
3337
|
var connections = /* @__PURE__ */ new Map();
|
|
@@ -3301,7 +3350,7 @@ function storeMessage(storeKey, entry) {
|
|
|
3301
3350
|
if (entries.length > MESSAGE_STORE_MAX) {
|
|
3302
3351
|
const trimmed = entries.length - MESSAGE_STORE_MAX;
|
|
3303
3352
|
entries.splice(0, trimmed);
|
|
3304
|
-
console.error(`${
|
|
3353
|
+
console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
3305
3354
|
}
|
|
3306
3355
|
}
|
|
3307
3356
|
function deriveCacheKey(input) {
|
|
@@ -3318,9 +3367,28 @@ function deriveCacheKey(input) {
|
|
|
3318
3367
|
}
|
|
3319
3368
|
return `whatsapp:${input.accountId}:${input.senderPhone}`;
|
|
3320
3369
|
}
|
|
3370
|
+
function buildSelfChatDispatch(input) {
|
|
3371
|
+
const senderPhone = input.selfPhone ?? "owner";
|
|
3372
|
+
return {
|
|
3373
|
+
accountId: input.accountId,
|
|
3374
|
+
agentType: "admin",
|
|
3375
|
+
senderPhone,
|
|
3376
|
+
text: input.text,
|
|
3377
|
+
isGroup: false,
|
|
3378
|
+
reply: input.reply,
|
|
3379
|
+
composing: input.composing,
|
|
3380
|
+
cacheKey: deriveCacheKey({
|
|
3381
|
+
agentType: "admin",
|
|
3382
|
+
accountId: input.accountId,
|
|
3383
|
+
senderPhone,
|
|
3384
|
+
isGroup: false
|
|
3385
|
+
}),
|
|
3386
|
+
media: []
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3321
3389
|
async function init(opts) {
|
|
3322
3390
|
if (initialized) {
|
|
3323
|
-
console.error(`${
|
|
3391
|
+
console.error(`${TAG13} already initialized`);
|
|
3324
3392
|
return;
|
|
3325
3393
|
}
|
|
3326
3394
|
configDir = opts.configDir;
|
|
@@ -3328,20 +3396,20 @@ async function init(opts) {
|
|
|
3328
3396
|
loadConfig(opts.accountConfig);
|
|
3329
3397
|
const accountIds = listCredentialAccountIds(configDir);
|
|
3330
3398
|
if (accountIds.length === 0) {
|
|
3331
|
-
console.error(`${
|
|
3399
|
+
console.error(`${TAG13} init: no stored WhatsApp credentials found`);
|
|
3332
3400
|
initialized = true;
|
|
3333
3401
|
return;
|
|
3334
3402
|
}
|
|
3335
|
-
console.error(`${
|
|
3403
|
+
console.error(`${TAG13} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
|
|
3336
3404
|
initialized = true;
|
|
3337
3405
|
for (const accountId of accountIds) {
|
|
3338
3406
|
const accountCfg = whatsAppConfig.accounts?.[accountId];
|
|
3339
3407
|
if (accountCfg?.enabled === false) {
|
|
3340
|
-
console.error(`${
|
|
3408
|
+
console.error(`${TAG13} skipping disabled account=${accountId}`);
|
|
3341
3409
|
continue;
|
|
3342
3410
|
}
|
|
3343
3411
|
startConnection(accountId).catch((err) => {
|
|
3344
|
-
console.error(`${
|
|
3412
|
+
console.error(`${TAG13} failed to auto-start account=${accountId}: ${formatError(err)}`);
|
|
3345
3413
|
});
|
|
3346
3414
|
}
|
|
3347
3415
|
}
|
|
@@ -3350,7 +3418,7 @@ async function startConnection(accountId) {
|
|
|
3350
3418
|
const authDir = resolveAuthDir(configDir, accountId);
|
|
3351
3419
|
const hasAuth = await authExists(authDir);
|
|
3352
3420
|
if (!hasAuth) {
|
|
3353
|
-
console.error(`${
|
|
3421
|
+
console.error(`${TAG13} no credentials for account=${accountId}`);
|
|
3354
3422
|
return;
|
|
3355
3423
|
}
|
|
3356
3424
|
let platformAccountId;
|
|
@@ -3400,11 +3468,11 @@ async function stopConnection(accountId) {
|
|
|
3400
3468
|
conn.sock.ev.removeAllListeners("creds.update");
|
|
3401
3469
|
conn.sock.ws?.close?.();
|
|
3402
3470
|
} catch (err) {
|
|
3403
|
-
console.warn(`${
|
|
3471
|
+
console.warn(`${TAG13} socket cleanup error during stop account=${accountId}: ${String(err)}`);
|
|
3404
3472
|
}
|
|
3405
3473
|
}
|
|
3406
3474
|
connections.delete(accountId);
|
|
3407
|
-
console.error(`${
|
|
3475
|
+
console.error(`${TAG13} stopped account=${accountId}`);
|
|
3408
3476
|
}
|
|
3409
3477
|
function getStatus() {
|
|
3410
3478
|
return Array.from(connections.values()).map((conn) => ({
|
|
@@ -3466,9 +3534,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
3466
3534
|
connections.set(accountId, conn);
|
|
3467
3535
|
try {
|
|
3468
3536
|
await sock.sendPresenceUpdate("available");
|
|
3469
|
-
console.error(`${
|
|
3537
|
+
console.error(`${TAG13} presence set to available account=${accountId}`);
|
|
3470
3538
|
} catch (err) {
|
|
3471
|
-
console.error(`${
|
|
3539
|
+
console.error(`${TAG13} presence update failed account=${accountId}: ${String(err)}`);
|
|
3472
3540
|
}
|
|
3473
3541
|
await runInitQueries(sock, {
|
|
3474
3542
|
accountId,
|
|
@@ -3480,7 +3548,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
3480
3548
|
monitorInbound(conn);
|
|
3481
3549
|
watchForDisconnect(conn);
|
|
3482
3550
|
attachSelfIdRefreshOnCredsUpdate(conn);
|
|
3483
|
-
console.error(`${
|
|
3551
|
+
console.error(`${TAG13} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
3484
3552
|
}
|
|
3485
3553
|
function attachSelfIdRefreshOnCredsUpdate(conn) {
|
|
3486
3554
|
if (!conn.sock) return;
|
|
@@ -3531,7 +3599,7 @@ async function shutdown() {
|
|
|
3531
3599
|
const ids = Array.from(connections.keys());
|
|
3532
3600
|
await Promise.all(ids.map((id) => stopConnection(id)));
|
|
3533
3601
|
initialized = false;
|
|
3534
|
-
console.error(`${
|
|
3602
|
+
console.error(`${TAG13} shutdown complete`);
|
|
3535
3603
|
}
|
|
3536
3604
|
function loadConfig(accountConfig) {
|
|
3537
3605
|
try {
|
|
@@ -3542,19 +3610,19 @@ function loadConfig(accountConfig) {
|
|
|
3542
3610
|
if (result.ok) {
|
|
3543
3611
|
whatsAppConfig = result.data;
|
|
3544
3612
|
if (result.droppedKeys.length > 0) {
|
|
3545
|
-
console.error(`${
|
|
3613
|
+
console.error(`${TAG13} config: dropped unknown keys=[${result.droppedKeys.join(",")}] preserved known config`);
|
|
3546
3614
|
}
|
|
3547
3615
|
} else {
|
|
3548
|
-
console.error(`${
|
|
3616
|
+
console.error(`${TAG13} config validation failed: ${result.error.message}`);
|
|
3549
3617
|
whatsAppConfig = {};
|
|
3550
3618
|
}
|
|
3551
3619
|
const diskAdmin = Array.isArray(wa.adminPhones) ? wa.adminPhones : [];
|
|
3552
3620
|
if (diskAdmin.length > 0 && (whatsAppConfig.adminPhones?.length ?? 0) === 0) {
|
|
3553
|
-
console.error(`${
|
|
3621
|
+
console.error(`${TAG13} WARN adminPhones present on disk but empty after load`);
|
|
3554
3622
|
}
|
|
3555
3623
|
}
|
|
3556
3624
|
} catch (err) {
|
|
3557
|
-
console.error(`${
|
|
3625
|
+
console.error(`${TAG13} config load error: ${String(err)}`);
|
|
3558
3626
|
whatsAppConfig = {};
|
|
3559
3627
|
}
|
|
3560
3628
|
}
|
|
@@ -3565,13 +3633,13 @@ async function connectWithReconnect(conn) {
|
|
|
3565
3633
|
let cycleError = null;
|
|
3566
3634
|
let connectedAt;
|
|
3567
3635
|
try {
|
|
3568
|
-
console.error(`${
|
|
3636
|
+
console.error(`${TAG13} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
|
|
3569
3637
|
const sock = await createWaSocket({
|
|
3570
3638
|
authDir: conn.authDir,
|
|
3571
3639
|
silent: true
|
|
3572
3640
|
});
|
|
3573
3641
|
conn.sock = sock;
|
|
3574
|
-
console.error(`${
|
|
3642
|
+
console.error(`${TAG13} socket created account=${conn.accountId} \u2014 waiting for connection`);
|
|
3575
3643
|
await waitForConnection(sock);
|
|
3576
3644
|
const selfId = readSelfId(conn.authDir);
|
|
3577
3645
|
connectedAt = Date.now();
|
|
@@ -3589,12 +3657,12 @@ async function connectWithReconnect(conn) {
|
|
|
3589
3657
|
} else {
|
|
3590
3658
|
console.error(`[whatsapp:lid] FAIL self-lid-resolution accountId=${conn.accountId} reason=creds-missing-lid`);
|
|
3591
3659
|
}
|
|
3592
|
-
console.error(`${
|
|
3660
|
+
console.error(`${TAG13} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
3593
3661
|
try {
|
|
3594
3662
|
await sock.sendPresenceUpdate("available");
|
|
3595
|
-
console.error(`${
|
|
3663
|
+
console.error(`${TAG13} presence set to available account=${conn.accountId}`);
|
|
3596
3664
|
} catch (err) {
|
|
3597
|
-
console.error(`${
|
|
3665
|
+
console.error(`${TAG13} presence update failed account=${conn.accountId}: ${String(err)}`);
|
|
3598
3666
|
}
|
|
3599
3667
|
await runInitQueries(sock, {
|
|
3600
3668
|
accountId: conn.accountId,
|
|
@@ -3622,10 +3690,10 @@ async function connectWithReconnect(conn) {
|
|
|
3622
3690
|
}
|
|
3623
3691
|
const classification = classifyDisconnect(err);
|
|
3624
3692
|
conn.lastError = classification.message;
|
|
3625
|
-
console.error(`${
|
|
3693
|
+
console.error(`${TAG13} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
|
|
3626
3694
|
if (!classification.shouldRetry) {
|
|
3627
3695
|
conn.terminalReason = classification.message;
|
|
3628
|
-
console.error(`${
|
|
3696
|
+
console.error(`${TAG13} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
|
|
3629
3697
|
return;
|
|
3630
3698
|
}
|
|
3631
3699
|
}
|
|
@@ -3638,7 +3706,7 @@ async function connectWithReconnect(conn) {
|
|
|
3638
3706
|
if (decision.action === "abort") {
|
|
3639
3707
|
const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
|
|
3640
3708
|
console.error(
|
|
3641
|
-
`${
|
|
3709
|
+
`${TAG13} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
|
|
3642
3710
|
);
|
|
3643
3711
|
conn.sessionStuckReason = stuckReason;
|
|
3644
3712
|
conn.lastError = stuckReason;
|
|
@@ -3647,17 +3715,17 @@ async function connectWithReconnect(conn) {
|
|
|
3647
3715
|
conn.reconnectAttempts = decision.nextAttempts;
|
|
3648
3716
|
if (decision.reason === "short-lived") {
|
|
3649
3717
|
console.error(
|
|
3650
|
-
`${
|
|
3718
|
+
`${TAG13} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
|
|
3651
3719
|
);
|
|
3652
3720
|
} else {
|
|
3653
3721
|
console.error(
|
|
3654
|
-
`${
|
|
3722
|
+
`${TAG13} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
|
|
3655
3723
|
);
|
|
3656
3724
|
}
|
|
3657
3725
|
if (decision.reason === "short-lived" || cycleError) {
|
|
3658
3726
|
const delay = computeBackoff(Math.max(1, decision.nextAttempts));
|
|
3659
3727
|
console.error(
|
|
3660
|
-
`${
|
|
3728
|
+
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
3661
3729
|
);
|
|
3662
3730
|
await new Promise((resolve28) => {
|
|
3663
3731
|
const timer2 = setTimeout(resolve28, delay);
|
|
@@ -3691,11 +3759,11 @@ function watchForDisconnect(conn) {
|
|
|
3691
3759
|
conn.sock.ev.on("connection.update", (update) => {
|
|
3692
3760
|
if (update.connection === "close") {
|
|
3693
3761
|
if (connections.get(conn.accountId) !== conn) return;
|
|
3694
|
-
console.error(`${
|
|
3762
|
+
console.error(`${TAG13} socket disconnected for account=${conn.accountId}`);
|
|
3695
3763
|
conn.connected = false;
|
|
3696
3764
|
conn.sock = null;
|
|
3697
3765
|
connectWithReconnect(conn).catch((err) => {
|
|
3698
|
-
console.error(`${
|
|
3766
|
+
console.error(`${TAG13} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
|
|
3699
3767
|
});
|
|
3700
3768
|
}
|
|
3701
3769
|
});
|
|
@@ -3704,7 +3772,7 @@ function monitorInbound(conn) {
|
|
|
3704
3772
|
if (!conn.sock || !onInboundMessage) return;
|
|
3705
3773
|
const sock = conn.sock;
|
|
3706
3774
|
const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
|
|
3707
|
-
console.error(`${
|
|
3775
|
+
console.error(`${TAG13} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
|
|
3708
3776
|
conn.debouncer = createInboundDebouncer({
|
|
3709
3777
|
debounceMs,
|
|
3710
3778
|
buildKey: (payload) => {
|
|
@@ -3714,12 +3782,12 @@ function monitorInbound(conn) {
|
|
|
3714
3782
|
onFlush: (entries) => {
|
|
3715
3783
|
if (!onInboundMessage) return;
|
|
3716
3784
|
if (entries.length > 1) {
|
|
3717
|
-
console.error(`${
|
|
3785
|
+
console.error(`${TAG13} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
|
|
3718
3786
|
}
|
|
3719
3787
|
onInboundMessage(combineInboundBatch(entries));
|
|
3720
3788
|
},
|
|
3721
3789
|
onError: (err) => {
|
|
3722
|
-
console.error(`${
|
|
3790
|
+
console.error(`${TAG13} debounce flush error account=${conn.accountId}: ${String(err)}`);
|
|
3723
3791
|
}
|
|
3724
3792
|
});
|
|
3725
3793
|
sock.ev.on("messages.upsert", async (upsert) => {
|
|
@@ -3747,7 +3815,7 @@ function monitorInbound(conn) {
|
|
|
3747
3815
|
});
|
|
3748
3816
|
const entries = messageStore.get(storeKey);
|
|
3749
3817
|
console.error(
|
|
3750
|
-
`${
|
|
3818
|
+
`${TAG13} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
|
|
3751
3819
|
);
|
|
3752
3820
|
recordActivity({
|
|
3753
3821
|
accountId: conn.accountId,
|
|
@@ -3810,14 +3878,46 @@ function monitorInbound(conn) {
|
|
|
3810
3878
|
isGroupJid(remoteJid) ? msg.key.participant ?? void 0 : void 0
|
|
3811
3879
|
);
|
|
3812
3880
|
}
|
|
3881
|
+
const decision = evaluateSelfChatCommand({
|
|
3882
|
+
accountId: conn.accountId,
|
|
3883
|
+
remoteJid,
|
|
3884
|
+
fromMe: Boolean(msg.key.fromMe),
|
|
3885
|
+
msgId: msg.key.id ?? null,
|
|
3886
|
+
messageTimestampSec: msg.messageTimestamp ? Number(msg.messageTimestamp) : null,
|
|
3887
|
+
lastConnectedAtMs: conn.lastConnectedAt ?? null,
|
|
3888
|
+
selfJid: conn.selfJid,
|
|
3889
|
+
selfLid: conn.selfLid,
|
|
3890
|
+
selfPhone: conn.selfPhone,
|
|
3891
|
+
text: extracted.text || null
|
|
3892
|
+
});
|
|
3893
|
+
if (decision.kind === "dispatch" && onInboundMessage) {
|
|
3894
|
+
const reply = async (text) => {
|
|
3895
|
+
const currentSock = conn.sock;
|
|
3896
|
+
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
3897
|
+
await sendTextMessage(currentSock, remoteJid, text, { accountId: conn.accountId });
|
|
3898
|
+
};
|
|
3899
|
+
const composing = async () => {
|
|
3900
|
+
const currentSock = conn.sock;
|
|
3901
|
+
if (currentSock) await sendComposing(currentSock, remoteJid);
|
|
3902
|
+
};
|
|
3903
|
+
onInboundMessage(buildSelfChatDispatch({
|
|
3904
|
+
accountId: conn.accountId,
|
|
3905
|
+
selfPhone: conn.selfPhone,
|
|
3906
|
+
remoteJid,
|
|
3907
|
+
text: decision.text,
|
|
3908
|
+
reply,
|
|
3909
|
+
composing
|
|
3910
|
+
}));
|
|
3911
|
+
continue;
|
|
3912
|
+
}
|
|
3813
3913
|
console.error(
|
|
3814
|
-
`${
|
|
3914
|
+
`${TAG13} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
|
|
3815
3915
|
);
|
|
3816
3916
|
continue;
|
|
3817
3917
|
}
|
|
3818
3918
|
await handleInboundMessage(conn, msg);
|
|
3819
3919
|
} catch (err) {
|
|
3820
|
-
console.error(`${
|
|
3920
|
+
console.error(`${TAG13} inbound handler error account=${conn.accountId}: ${String(err)}`);
|
|
3821
3921
|
}
|
|
3822
3922
|
}
|
|
3823
3923
|
});
|
|
@@ -3827,31 +3927,31 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3827
3927
|
const remoteJid = msg.key.remoteJid;
|
|
3828
3928
|
if (!remoteJid) return;
|
|
3829
3929
|
if (remoteJid === "status@broadcast") {
|
|
3830
|
-
console.error(`${
|
|
3930
|
+
console.error(`${TAG13} drop: status broadcast account=${conn.accountId}`);
|
|
3831
3931
|
return;
|
|
3832
3932
|
}
|
|
3833
3933
|
if (!msg.message) {
|
|
3834
|
-
console.error(`${
|
|
3934
|
+
console.error(`${TAG13} drop: empty message account=${conn.accountId} from=${remoteJid}`);
|
|
3835
3935
|
return;
|
|
3836
3936
|
}
|
|
3837
3937
|
const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
|
|
3838
3938
|
if (isDuplicateInbound(dedupKey)) {
|
|
3839
|
-
console.error(`${
|
|
3939
|
+
console.error(`${TAG13} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
|
|
3840
3940
|
return;
|
|
3841
3941
|
}
|
|
3842
3942
|
if (msg.key.fromMe) {
|
|
3843
3943
|
if (msg.key.id && isAgentSentMessage(msg.key.id)) {
|
|
3844
|
-
console.error(`${
|
|
3944
|
+
console.error(`${TAG13} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
|
|
3845
3945
|
return;
|
|
3846
3946
|
}
|
|
3847
3947
|
const extracted2 = extractMessage(msg);
|
|
3848
3948
|
if (!extracted2.text) {
|
|
3849
|
-
console.error(`${
|
|
3949
|
+
console.error(`${TAG13} owner reply skipped \u2014 no text content account=${conn.accountId}`);
|
|
3850
3950
|
return;
|
|
3851
3951
|
}
|
|
3852
3952
|
const isGroup2 = isGroupJid(remoteJid);
|
|
3853
3953
|
const senderPhone2 = conn.selfPhone ?? "owner";
|
|
3854
|
-
console.error(`${
|
|
3954
|
+
console.error(`${TAG13} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
|
|
3855
3955
|
const reply2 = async (text) => {
|
|
3856
3956
|
const currentSock = conn.sock;
|
|
3857
3957
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
@@ -3885,7 +3985,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3885
3985
|
}
|
|
3886
3986
|
const extracted = extractMessage(msg);
|
|
3887
3987
|
if (!extracted.text && !extracted.mediaType) {
|
|
3888
|
-
console.error(`${
|
|
3988
|
+
console.error(`${TAG13} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
|
|
3889
3989
|
return;
|
|
3890
3990
|
}
|
|
3891
3991
|
let mediaResult;
|
|
@@ -3895,7 +3995,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3895
3995
|
maxBytes: maxMb * 1024 * 1024
|
|
3896
3996
|
});
|
|
3897
3997
|
if (!mediaResult) {
|
|
3898
|
-
console.error(`${
|
|
3998
|
+
console.error(`${TAG13} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
3899
3999
|
}
|
|
3900
4000
|
}
|
|
3901
4001
|
const isGroup = isGroupJid(remoteJid);
|
|
@@ -3933,7 +4033,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3933
4033
|
});
|
|
3934
4034
|
}
|
|
3935
4035
|
console.error(
|
|
3936
|
-
`${
|
|
4036
|
+
`${TAG13} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
|
|
3937
4037
|
);
|
|
3938
4038
|
if (!accessResult.allowed) return;
|
|
3939
4039
|
const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
|
|
@@ -3942,7 +4042,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3942
4042
|
}
|
|
3943
4043
|
const reply = async (text) => {
|
|
3944
4044
|
if (isGroupJid(remoteJid)) {
|
|
3945
|
-
console.error(`${
|
|
4045
|
+
console.error(`${TAG13} op=group-reply-blocked jid=${remoteJid}`);
|
|
3946
4046
|
throw new WaReplyError("group reply blocked \u2014 observe-only", false);
|
|
3947
4047
|
}
|
|
3948
4048
|
const currentSock = conn.sock;
|
|
@@ -3980,15 +4080,15 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3980
4080
|
if (accessResult.agentType === "public") {
|
|
3981
4081
|
const hoursResult = await isBusinessOpen(conn.accountId);
|
|
3982
4082
|
if (!hoursResult.open) {
|
|
3983
|
-
console.error(`${
|
|
4083
|
+
console.error(`${TAG13} [${conn.accountId}] dispatch skipped: business closed`);
|
|
3984
4084
|
const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
|
|
3985
4085
|
if (afterHoursMessage) {
|
|
3986
4086
|
try {
|
|
3987
4087
|
await reply(afterHoursMessage);
|
|
3988
|
-
console.error(`${
|
|
4088
|
+
console.error(`${TAG13} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
|
|
3989
4089
|
} catch (err) {
|
|
3990
4090
|
console.error(
|
|
3991
|
-
`${
|
|
4091
|
+
`${TAG13} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3992
4092
|
);
|
|
3993
4093
|
}
|
|
3994
4094
|
}
|
|
@@ -4502,7 +4602,7 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
|
|
|
4502
4602
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
4503
4603
|
import { tmpdir } from "os";
|
|
4504
4604
|
import { join as join3 } from "path";
|
|
4505
|
-
var
|
|
4605
|
+
var TAG14 = "[voice]";
|
|
4506
4606
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
4507
4607
|
"audio/ogg",
|
|
4508
4608
|
"audio/opus",
|
|
@@ -4540,7 +4640,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4540
4640
|
const bytes = file.size;
|
|
4541
4641
|
const mimeType = file.type;
|
|
4542
4642
|
console.error(
|
|
4543
|
-
`${
|
|
4643
|
+
`${TAG14} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
|
|
4544
4644
|
);
|
|
4545
4645
|
let tempDir;
|
|
4546
4646
|
let tempPath;
|
|
@@ -4552,7 +4652,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4552
4652
|
await writeFile3(tempPath, buffer);
|
|
4553
4653
|
} catch (err) {
|
|
4554
4654
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4555
|
-
console.error(`${
|
|
4655
|
+
console.error(`${TAG14} failed source=${source} error=temp-file-write: ${reason}`);
|
|
4556
4656
|
return { ok: false, error: "Could not process voice note" };
|
|
4557
4657
|
}
|
|
4558
4658
|
try {
|
|
@@ -4560,7 +4660,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4560
4660
|
if (!sttResult) {
|
|
4561
4661
|
const elapsed2 = Date.now() - startMs;
|
|
4562
4662
|
console.error(
|
|
4563
|
-
`${
|
|
4663
|
+
`${TAG14} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
|
|
4564
4664
|
);
|
|
4565
4665
|
return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
|
|
4566
4666
|
}
|
|
@@ -4568,7 +4668,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4568
4668
|
const elapsed = Date.now() - startMs;
|
|
4569
4669
|
const words = rawText.split(/\s+/).filter(Boolean).length;
|
|
4570
4670
|
console.error(
|
|
4571
|
-
`${
|
|
4671
|
+
`${TAG14} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
|
|
4572
4672
|
);
|
|
4573
4673
|
return {
|
|
4574
4674
|
ok: true,
|
|
@@ -4578,7 +4678,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4578
4678
|
const elapsed = Date.now() - startMs;
|
|
4579
4679
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4580
4680
|
console.error(
|
|
4581
|
-
`${
|
|
4681
|
+
`${TAG14} failed source=${source} error=${reason} duration_ms=${elapsed}`
|
|
4582
4682
|
);
|
|
4583
4683
|
return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
|
|
4584
4684
|
} finally {
|
|
@@ -4647,6 +4747,7 @@ async function managerRcSpawn(opts) {
|
|
|
4647
4747
|
personId: opts.personId,
|
|
4648
4748
|
userId: opts.userId,
|
|
4649
4749
|
waChannel: opts.waChannel,
|
|
4750
|
+
webchatChannel: opts.webchatChannel,
|
|
4650
4751
|
role: opts.role,
|
|
4651
4752
|
channel: opts.channel,
|
|
4652
4753
|
senderId: opts.senderId
|
|
@@ -4718,7 +4819,7 @@ function adminSessionIdFor(accountId, senderId) {
|
|
|
4718
4819
|
|
|
4719
4820
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
4720
4821
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4721
|
-
var
|
|
4822
|
+
var TAG15 = "[public-session-review]";
|
|
4722
4823
|
var CONSUMED_CAP = 500;
|
|
4723
4824
|
var consumed = /* @__PURE__ */ new Set();
|
|
4724
4825
|
function consumeOnce(sessionId) {
|
|
@@ -4745,7 +4846,7 @@ async function fetchJsonl(sessionId) {
|
|
|
4745
4846
|
return await res.text();
|
|
4746
4847
|
} catch (err) {
|
|
4747
4848
|
console.error(
|
|
4748
|
-
`${
|
|
4849
|
+
`${TAG15} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
4749
4850
|
);
|
|
4750
4851
|
return null;
|
|
4751
4852
|
}
|
|
@@ -4769,7 +4870,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
4769
4870
|
const res = await fetch(url);
|
|
4770
4871
|
if (!res.ok) {
|
|
4771
4872
|
console.error(
|
|
4772
|
-
`${
|
|
4873
|
+
`${TAG15} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
4773
4874
|
);
|
|
4774
4875
|
return [];
|
|
4775
4876
|
}
|
|
@@ -4777,7 +4878,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
4777
4878
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
4778
4879
|
} catch (err) {
|
|
4779
4880
|
console.error(
|
|
4780
|
-
`${
|
|
4881
|
+
`${TAG15} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
4781
4882
|
);
|
|
4782
4883
|
return [];
|
|
4783
4884
|
}
|
|
@@ -4806,7 +4907,7 @@ function composeInitialMessage(input) {
|
|
|
4806
4907
|
}
|
|
4807
4908
|
async function dispatchReviewer(input, initialMessage) {
|
|
4808
4909
|
const sessionId = randomUUID4();
|
|
4809
|
-
console.log(`${
|
|
4910
|
+
console.log(`${TAG15} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
4810
4911
|
const spawned = await managerRcSpawn({
|
|
4811
4912
|
sessionId,
|
|
4812
4913
|
initialMessage,
|
|
@@ -4826,21 +4927,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
4826
4927
|
const dispatchedAt = Date.now();
|
|
4827
4928
|
if (consumed.has(input.sessionId)) {
|
|
4828
4929
|
console.log(
|
|
4829
|
-
`${
|
|
4930
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
4830
4931
|
);
|
|
4831
4932
|
return;
|
|
4832
4933
|
}
|
|
4833
4934
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
4834
4935
|
if (!jsonl) {
|
|
4835
4936
|
console.log(
|
|
4836
|
-
`${
|
|
4937
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
4837
4938
|
);
|
|
4838
4939
|
return;
|
|
4839
4940
|
}
|
|
4840
4941
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
4841
4942
|
if (operatorTurns === 0) {
|
|
4842
4943
|
console.log(
|
|
4843
|
-
`${
|
|
4944
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
4844
4945
|
);
|
|
4845
4946
|
return;
|
|
4846
4947
|
}
|
|
@@ -4854,19 +4955,19 @@ async function firePublicSessionEndReview(input) {
|
|
|
4854
4955
|
});
|
|
4855
4956
|
if (!consumeOnce(input.sessionId)) {
|
|
4856
4957
|
console.log(
|
|
4857
|
-
`${
|
|
4958
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
4858
4959
|
);
|
|
4859
4960
|
return;
|
|
4860
4961
|
}
|
|
4861
4962
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
4862
4963
|
if (!dispatched.ok) {
|
|
4863
4964
|
console.error(
|
|
4864
|
-
`${
|
|
4965
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
4865
4966
|
);
|
|
4866
4967
|
return;
|
|
4867
4968
|
}
|
|
4868
4969
|
console.log(
|
|
4869
|
-
`${
|
|
4970
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
|
|
4870
4971
|
);
|
|
4871
4972
|
}
|
|
4872
4973
|
|
|
@@ -5040,21 +5141,21 @@ async function fanOut(subscribers, text, onError, tag) {
|
|
|
5040
5141
|
import { realpathSync as realpathSync2 } from "fs";
|
|
5041
5142
|
import { readFile, stat as stat2 } from "fs/promises";
|
|
5042
5143
|
import { resolve as resolve4, basename } from "path";
|
|
5043
|
-
var
|
|
5144
|
+
var TAG16 = "[whatsapp:outbound]";
|
|
5044
5145
|
var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
|
|
5045
5146
|
var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
5046
5147
|
var lastRouteDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
5047
|
-
function
|
|
5148
|
+
function normalizeJid2(to) {
|
|
5048
5149
|
return to.includes("@") ? to : toWhatsappJid(to);
|
|
5049
5150
|
}
|
|
5050
5151
|
function routeKey(to, filePath) {
|
|
5051
|
-
return `${
|
|
5152
|
+
return `${normalizeJid2(to)}\0${filePath}`;
|
|
5052
5153
|
}
|
|
5053
5154
|
function documentOutboundAt(to) {
|
|
5054
|
-
return lastDocumentOutboundAt.get(
|
|
5155
|
+
return lastDocumentOutboundAt.get(normalizeJid2(to));
|
|
5055
5156
|
}
|
|
5056
5157
|
function recordDocumentOutbound(to) {
|
|
5057
|
-
lastDocumentOutboundAt.set(
|
|
5158
|
+
lastDocumentOutboundAt.set(normalizeJid2(to), Date.now());
|
|
5058
5159
|
}
|
|
5059
5160
|
function recordRouteDocumentOutbound(to, filePath) {
|
|
5060
5161
|
lastRouteDocumentOutboundAt.set(routeKey(to, filePath), Date.now());
|
|
@@ -5077,16 +5178,16 @@ async function sendWhatsAppDocument(input) {
|
|
|
5077
5178
|
const accountResolved = realpathSync2(accountDir);
|
|
5078
5179
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
5079
5180
|
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
5080
|
-
console.error(`${
|
|
5181
|
+
console.error(`${TAG16} document REJECTED path=${sanitised} reason=outside_account_directory`);
|
|
5081
5182
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
5082
5183
|
}
|
|
5083
5184
|
} catch (err) {
|
|
5084
5185
|
const code = err.code;
|
|
5085
5186
|
if (code === "ENOENT") {
|
|
5086
|
-
console.error(`${
|
|
5187
|
+
console.error(`${TAG16} document ENOENT path=${filePath}`);
|
|
5087
5188
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
5088
5189
|
}
|
|
5089
|
-
console.error(`${
|
|
5190
|
+
console.error(`${TAG16} document path error: ${String(err)}`);
|
|
5090
5191
|
return { ok: false, status: 500, error: String(err) };
|
|
5091
5192
|
}
|
|
5092
5193
|
const fileStat = await stat2(resolvedPath);
|
|
@@ -5098,10 +5199,10 @@ async function sendWhatsAppDocument(input) {
|
|
|
5098
5199
|
};
|
|
5099
5200
|
}
|
|
5100
5201
|
const filename = basename(resolvedPath);
|
|
5101
|
-
const jid =
|
|
5202
|
+
const jid = normalizeJid2(to);
|
|
5102
5203
|
const sock = getSocket(accountId);
|
|
5103
5204
|
if (!sock) {
|
|
5104
|
-
console.error(`${
|
|
5205
|
+
console.error(`${TAG16} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=false reason=not-connected`);
|
|
5105
5206
|
return { ok: false, status: 503, error: `WhatsApp account "${accountId}" is not connected` };
|
|
5106
5207
|
}
|
|
5107
5208
|
const buffer = Buffer.from(await readFile(resolvedPath));
|
|
@@ -5113,7 +5214,7 @@ async function sendWhatsAppDocument(input) {
|
|
|
5113
5214
|
{ accountId }
|
|
5114
5215
|
);
|
|
5115
5216
|
console.error(
|
|
5116
|
-
`${
|
|
5217
|
+
`${TAG16} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
|
|
5117
5218
|
);
|
|
5118
5219
|
if (result.success) {
|
|
5119
5220
|
recordDocumentOutbound(to);
|
|
@@ -5123,7 +5224,7 @@ async function sendWhatsAppDocument(input) {
|
|
|
5123
5224
|
}
|
|
5124
5225
|
|
|
5125
5226
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
5126
|
-
var
|
|
5227
|
+
var TAG17 = "[whatsapp-adaptor]";
|
|
5127
5228
|
var SEND_USER_FILE = "SendUserFile";
|
|
5128
5229
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
5129
5230
|
function platformRoot() {
|
|
@@ -5158,7 +5259,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
5158
5259
|
maxyAccountId = resolvePlatformAccountId();
|
|
5159
5260
|
} catch (err) {
|
|
5160
5261
|
console.error(
|
|
5161
|
-
`${
|
|
5262
|
+
`${TAG17} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
5162
5263
|
);
|
|
5163
5264
|
return;
|
|
5164
5265
|
}
|
|
@@ -5175,7 +5276,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
5175
5276
|
if (!result.ok) {
|
|
5176
5277
|
failedFiles.push(files[i]);
|
|
5177
5278
|
console.error(
|
|
5178
|
-
`${
|
|
5279
|
+
`${TAG17} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
5179
5280
|
);
|
|
5180
5281
|
}
|
|
5181
5282
|
}
|
|
@@ -5192,7 +5293,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
5192
5293
|
const sid = entry.sessionId.slice(0, 8);
|
|
5193
5294
|
for (const file of failed) {
|
|
5194
5295
|
console.error(
|
|
5195
|
-
`${
|
|
5296
|
+
`${TAG17} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
|
|
5196
5297
|
);
|
|
5197
5298
|
}
|
|
5198
5299
|
const okAt = documentOutboundAt(entry.senderId);
|
|
@@ -5203,13 +5304,13 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
5203
5304
|
if (!delivered) {
|
|
5204
5305
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
5205
5306
|
console.error(
|
|
5206
|
-
`${
|
|
5307
|
+
`${TAG17} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
5207
5308
|
);
|
|
5208
5309
|
}
|
|
5209
5310
|
}
|
|
5210
5311
|
if (firedTools.includes(SEND_USER_FILE) && attempts === 0 && !documentWentOut) {
|
|
5211
5312
|
console.error(
|
|
5212
|
-
`${
|
|
5313
|
+
`${TAG17} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
|
|
5213
5314
|
);
|
|
5214
5315
|
}
|
|
5215
5316
|
}
|
|
@@ -5791,7 +5892,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
5791
5892
|
// app/lib/whatsapp/config-persist.ts
|
|
5792
5893
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
|
|
5793
5894
|
import { resolve as resolve6, join as join4 } from "path";
|
|
5794
|
-
var
|
|
5895
|
+
var TAG18 = "[whatsapp:config]";
|
|
5795
5896
|
function configPath(accountDir) {
|
|
5796
5897
|
return resolve6(accountDir, "account.json");
|
|
5797
5898
|
}
|
|
@@ -5808,9 +5909,9 @@ function reloadManagerConfig(accountDir) {
|
|
|
5808
5909
|
try {
|
|
5809
5910
|
const config = readConfig(accountDir);
|
|
5810
5911
|
reloadConfig(config);
|
|
5811
|
-
console.error(`${
|
|
5912
|
+
console.error(`${TAG18} reloaded manager config`);
|
|
5812
5913
|
} catch (err) {
|
|
5813
|
-
console.error(`${
|
|
5914
|
+
console.error(`${TAG18} manager config reload failed: ${String(err)}`);
|
|
5814
5915
|
}
|
|
5815
5916
|
}
|
|
5816
5917
|
var E164_PATTERN = /^\+\d{7,15}$/;
|
|
@@ -5836,25 +5937,25 @@ function persistAfterPairing(accountDir, accountId, selfPhone) {
|
|
|
5836
5937
|
const adminPhones = wa.adminPhones;
|
|
5837
5938
|
if (!adminPhones.includes(normalized)) {
|
|
5838
5939
|
adminPhones.push(normalized);
|
|
5839
|
-
console.error(`${
|
|
5940
|
+
console.error(`${TAG18} added selfPhone=${normalized} to adminPhones`);
|
|
5840
5941
|
}
|
|
5841
5942
|
} else {
|
|
5842
|
-
console.error(`${
|
|
5943
|
+
console.error(`${TAG18} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
|
|
5843
5944
|
}
|
|
5844
5945
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5845
5946
|
if (!parsed.success) {
|
|
5846
5947
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5847
|
-
console.error(`${
|
|
5948
|
+
console.error(`${TAG18} validation failed after pairing: ${msg}`);
|
|
5848
5949
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5849
5950
|
}
|
|
5850
5951
|
config.whatsapp = parsed.data;
|
|
5851
5952
|
writeConfig(accountDir, config);
|
|
5852
|
-
console.error(`${
|
|
5953
|
+
console.error(`${TAG18} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
|
|
5853
5954
|
reloadManagerConfig(accountDir);
|
|
5854
5955
|
return { ok: true };
|
|
5855
5956
|
} catch (err) {
|
|
5856
5957
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5857
|
-
console.error(`${
|
|
5958
|
+
console.error(`${TAG18} persist failed account=${accountId}: ${msg}`);
|
|
5858
5959
|
return { ok: false, error: msg };
|
|
5859
5960
|
}
|
|
5860
5961
|
}
|
|
@@ -5884,12 +5985,12 @@ function addAdminPhone(accountDir, phone) {
|
|
|
5884
5985
|
}
|
|
5885
5986
|
config.whatsapp = parsed.data;
|
|
5886
5987
|
writeConfig(accountDir, config);
|
|
5887
|
-
console.error(`${
|
|
5988
|
+
console.error(`${TAG18} added admin phone=${normalized}`);
|
|
5888
5989
|
reloadManagerConfig(accountDir);
|
|
5889
5990
|
return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
|
|
5890
5991
|
} catch (err) {
|
|
5891
5992
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5892
|
-
console.error(`${
|
|
5993
|
+
console.error(`${TAG18} addAdminPhone failed: ${msg}`);
|
|
5893
5994
|
return { ok: false, error: msg };
|
|
5894
5995
|
}
|
|
5895
5996
|
}
|
|
@@ -5917,12 +6018,12 @@ function removeAdminPhone(accountDir, phone) {
|
|
|
5917
6018
|
}
|
|
5918
6019
|
config.whatsapp = parsed.data;
|
|
5919
6020
|
writeConfig(accountDir, config);
|
|
5920
|
-
console.error(`${
|
|
6021
|
+
console.error(`${TAG18} removed admin phone=${normalized}`);
|
|
5921
6022
|
reloadManagerConfig(accountDir);
|
|
5922
6023
|
return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
|
|
5923
6024
|
} catch (err) {
|
|
5924
6025
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5925
|
-
console.error(`${
|
|
6026
|
+
console.error(`${TAG18} removeAdminPhone failed: ${msg}`);
|
|
5926
6027
|
return { ok: false, error: msg };
|
|
5927
6028
|
}
|
|
5928
6029
|
}
|
|
@@ -5960,12 +6061,12 @@ function setPublicAgent(accountDir, slug) {
|
|
|
5960
6061
|
}
|
|
5961
6062
|
config.whatsapp = parsed.data;
|
|
5962
6063
|
writeConfig(accountDir, config);
|
|
5963
|
-
console.error(`${
|
|
6064
|
+
console.error(`${TAG18} publicAgent set to ${trimmed}`);
|
|
5964
6065
|
reloadManagerConfig(accountDir);
|
|
5965
6066
|
return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
|
|
5966
6067
|
} catch (err) {
|
|
5967
6068
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5968
|
-
console.error(`${
|
|
6069
|
+
console.error(`${TAG18} setPublicAgent failed: ${msg}`);
|
|
5969
6070
|
return { ok: false, error: msg };
|
|
5970
6071
|
}
|
|
5971
6072
|
}
|
|
@@ -6006,17 +6107,17 @@ function updateConfig(accountDir, fields) {
|
|
|
6006
6107
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6007
6108
|
if (!parsed.success) {
|
|
6008
6109
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6009
|
-
console.error(`${
|
|
6110
|
+
console.error(`${TAG18} update validation failed: ${msg}`);
|
|
6010
6111
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6011
6112
|
}
|
|
6012
6113
|
config.whatsapp = parsed.data;
|
|
6013
6114
|
writeConfig(accountDir, config);
|
|
6014
|
-
console.error(`${
|
|
6115
|
+
console.error(`${TAG18} updated fields=[${fieldNames.join(",")}]`);
|
|
6015
6116
|
reloadManagerConfig(accountDir);
|
|
6016
6117
|
return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
|
|
6017
6118
|
} catch (err) {
|
|
6018
6119
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6019
|
-
console.error(`${
|
|
6120
|
+
console.error(`${TAG18} updateConfig failed: ${msg}`);
|
|
6020
6121
|
return { ok: false, error: msg };
|
|
6021
6122
|
}
|
|
6022
6123
|
}
|
|
@@ -6053,17 +6154,17 @@ function migrateRemovedConfigKeys(accountDir) {
|
|
|
6053
6154
|
}
|
|
6054
6155
|
writeConfig(accountDir, config);
|
|
6055
6156
|
console.error(
|
|
6056
|
-
`${
|
|
6157
|
+
`${TAG18} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
|
|
6057
6158
|
);
|
|
6058
6159
|
return { dropped: result.droppedKeys };
|
|
6059
6160
|
} catch (err) {
|
|
6060
|
-
console.error(`${
|
|
6161
|
+
console.error(`${TAG18} migration failed: ${String(err)}`);
|
|
6061
6162
|
return { dropped: [] };
|
|
6062
6163
|
}
|
|
6063
6164
|
}
|
|
6064
6165
|
|
|
6065
6166
|
// app/lib/whatsapp/login.ts
|
|
6066
|
-
var
|
|
6167
|
+
var TAG19 = "[whatsapp:login]";
|
|
6067
6168
|
function maskPhone(digits) {
|
|
6068
6169
|
if (digits.length <= 4) return "*".repeat(digits.length);
|
|
6069
6170
|
return digits.slice(0, 2) + "*".repeat(digits.length - 4) + digits.slice(-2);
|
|
@@ -6074,7 +6175,7 @@ function closeSocket(sock) {
|
|
|
6074
6175
|
try {
|
|
6075
6176
|
sock.ws?.close?.();
|
|
6076
6177
|
} catch (err) {
|
|
6077
|
-
console.warn(`${
|
|
6178
|
+
console.warn(`${TAG19} socket close error during cleanup: ${String(err)}`);
|
|
6078
6179
|
}
|
|
6079
6180
|
}
|
|
6080
6181
|
function resetActiveLogin(accountId) {
|
|
@@ -6101,18 +6202,18 @@ async function runPostPairing(login) {
|
|
|
6101
6202
|
const masked = selfPhone ? `${selfPhone.slice(0, 4)}***` : "null";
|
|
6102
6203
|
console.error(`[whatsapp-persist] wa-persist-on-connect account=${login.accountId} phone=${masked}`);
|
|
6103
6204
|
} else {
|
|
6104
|
-
console.error(`${
|
|
6205
|
+
console.error(`${TAG19} wa-persist-on-connect FAILED account=${login.accountId}: ${result.error}`);
|
|
6105
6206
|
}
|
|
6106
6207
|
} catch (err) {
|
|
6107
|
-
console.error(`${
|
|
6208
|
+
console.error(`${TAG19} wa-persist-on-connect error account=${login.accountId}: ${String(err)}`);
|
|
6108
6209
|
}
|
|
6109
6210
|
} else {
|
|
6110
|
-
console.error(`${
|
|
6211
|
+
console.error(`${TAG19} wa-persist-on-connect skipped \u2014 no accountDir account=${login.accountId}`);
|
|
6111
6212
|
}
|
|
6112
6213
|
try {
|
|
6113
6214
|
await registerLoginSocket(login.accountId, login.sock, login.authDir);
|
|
6114
6215
|
} catch (err) {
|
|
6115
|
-
console.error(`${
|
|
6216
|
+
console.error(`${TAG19} registerLoginSocket failed account=${login.accountId}: ${String(err)}`);
|
|
6116
6217
|
}
|
|
6117
6218
|
}
|
|
6118
6219
|
async function loginConnectionLoop(accountId, login) {
|
|
@@ -6124,7 +6225,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6124
6225
|
if (current?.id === login.id) {
|
|
6125
6226
|
await runPostPairing(current);
|
|
6126
6227
|
current.connected = true;
|
|
6127
|
-
console.error(`${
|
|
6228
|
+
console.error(`${TAG19} loginConnectionLoop: connected account=${accountId} attempt=${attempt} configPersisted=${current.configPersisted}`);
|
|
6128
6229
|
}
|
|
6129
6230
|
return;
|
|
6130
6231
|
} catch (err) {
|
|
@@ -6134,7 +6235,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6134
6235
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6135
6236
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6136
6237
|
console.error(
|
|
6137
|
-
`${
|
|
6238
|
+
`${TAG19} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
6138
6239
|
);
|
|
6139
6240
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
6140
6241
|
} else {
|
|
@@ -6146,7 +6247,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6146
6247
|
attempt++;
|
|
6147
6248
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
6148
6249
|
console.error(
|
|
6149
|
-
`${
|
|
6250
|
+
`${TAG19} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
6150
6251
|
);
|
|
6151
6252
|
closeSocket(current.sock);
|
|
6152
6253
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -6157,7 +6258,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6157
6258
|
current.sock = newSock;
|
|
6158
6259
|
} catch (sockErr) {
|
|
6159
6260
|
console.error(
|
|
6160
|
-
`${
|
|
6261
|
+
`${TAG19} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
6161
6262
|
);
|
|
6162
6263
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
6163
6264
|
return;
|
|
@@ -6171,7 +6272,7 @@ async function startLogin(opts) {
|
|
|
6171
6272
|
const hasAuth = await authExists(authDir);
|
|
6172
6273
|
const selfId = readSelfId(authDir);
|
|
6173
6274
|
console.error(
|
|
6174
|
-
`${
|
|
6275
|
+
`${TAG19} startLogin account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasCode=${!!existing0.pairingCode}}` : " activeLogin=none")
|
|
6175
6276
|
);
|
|
6176
6277
|
if (hasAuth && !force) {
|
|
6177
6278
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -6186,7 +6287,7 @@ async function startLogin(opts) {
|
|
|
6186
6287
|
}
|
|
6187
6288
|
const existing = activeLogins.get(accountId);
|
|
6188
6289
|
if (existing && isLoginFresh(existing) && existing.pairingCode && !force) {
|
|
6189
|
-
console.error(`${
|
|
6290
|
+
console.error(`${TAG19} startLogin account=${accountId} guard: returning existing pairing code (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
6190
6291
|
return {
|
|
6191
6292
|
pairingCode: existing.pairingCode,
|
|
6192
6293
|
phone: existing.phone,
|
|
@@ -6194,7 +6295,7 @@ async function startLogin(opts) {
|
|
|
6194
6295
|
};
|
|
6195
6296
|
}
|
|
6196
6297
|
if (existing) {
|
|
6197
|
-
console.error(`${
|
|
6298
|
+
console.error(`${TAG19} startLogin account=${accountId} ${force ? "force override" : "stale/no-code"}, resetting active login`);
|
|
6198
6299
|
}
|
|
6199
6300
|
resetActiveLogin(accountId);
|
|
6200
6301
|
await clearAuth(authDir);
|
|
@@ -6224,7 +6325,7 @@ async function startLogin(opts) {
|
|
|
6224
6325
|
const current = activeLogins.get(accountId);
|
|
6225
6326
|
if (current?.id !== login.id) return;
|
|
6226
6327
|
if (!current.pairingCode) current.pairingCode = code;
|
|
6227
|
-
console.error(`${
|
|
6328
|
+
console.error(`${TAG19} pairing-code-issued account=${accountId} phone=${maskPhone(digits)} codeLen=${code.length}`);
|
|
6228
6329
|
resolveCode?.(code);
|
|
6229
6330
|
}).catch((err) => {
|
|
6230
6331
|
clearTimeout(codeTimer);
|
|
@@ -6253,7 +6354,7 @@ async function startLogin(opts) {
|
|
|
6253
6354
|
const ttlTimer = setTimeout(() => {
|
|
6254
6355
|
const current = activeLogins.get(accountId);
|
|
6255
6356
|
if (current?.id === login.id && !current.connected) {
|
|
6256
|
-
console.error(`${
|
|
6357
|
+
console.error(`${TAG19} pairing-window-elapsed account=${accountId} (ttl sweep)`);
|
|
6257
6358
|
resetActiveLogin(accountId);
|
|
6258
6359
|
}
|
|
6259
6360
|
}, ACTIVE_LOGIN_TTL_MS);
|
|
@@ -6261,7 +6362,7 @@ async function startLogin(opts) {
|
|
|
6261
6362
|
ttlTimer.unref();
|
|
6262
6363
|
}
|
|
6263
6364
|
loginConnectionLoop(accountId, login).catch((err) => {
|
|
6264
|
-
console.error(`${
|
|
6365
|
+
console.error(`${TAG19} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
6265
6366
|
const current = activeLogins.get(accountId);
|
|
6266
6367
|
if (current?.id === login.id) {
|
|
6267
6368
|
current.error = `Unexpected login error: ${String(err)}`;
|
|
@@ -6286,13 +6387,13 @@ async function waitForLogin(opts) {
|
|
|
6286
6387
|
const { accountId, timeoutMs = 6e4 } = opts;
|
|
6287
6388
|
const login = activeLogins.get(accountId);
|
|
6288
6389
|
console.error(
|
|
6289
|
-
`${
|
|
6390
|
+
`${TAG19} waitForLogin account=${accountId} timeout=${timeoutMs}ms` + (login ? ` login={id=${login.id.slice(0, 8)},age=${Math.round((Date.now() - login.startedAt) / 1e3)}s,connected=${login.connected},hasCode=${!!login.pairingCode}}` : " login=none")
|
|
6290
6391
|
);
|
|
6291
6392
|
if (!login) {
|
|
6292
6393
|
return { connected: false, message: "No active WhatsApp login in progress.", configPersisted: false };
|
|
6293
6394
|
}
|
|
6294
6395
|
if (!isLoginFresh(login)) {
|
|
6295
|
-
console.error(`${
|
|
6396
|
+
console.error(`${TAG19} pairing-window-elapsed account=${accountId}`);
|
|
6296
6397
|
resetActiveLogin(accountId);
|
|
6297
6398
|
return { connected: false, message: "The pairing window expired. Ask me to generate a new code.", configPersisted: false };
|
|
6298
6399
|
}
|
|
@@ -6300,8 +6401,8 @@ async function waitForLogin(opts) {
|
|
|
6300
6401
|
while (Date.now() < deadline) {
|
|
6301
6402
|
if (login.connected) {
|
|
6302
6403
|
const selfId = readSelfId(login.authDir);
|
|
6303
|
-
console.error(`${
|
|
6304
|
-
console.error(`${
|
|
6404
|
+
console.error(`${TAG19} pairing-connected account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
6405
|
+
console.error(`${TAG19} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"} configPersisted=${login.configPersisted}`);
|
|
6305
6406
|
const configPersisted = login.configPersisted;
|
|
6306
6407
|
activeLogins.delete(accountId);
|
|
6307
6408
|
return {
|
|
@@ -6319,7 +6420,7 @@ async function waitForLogin(opts) {
|
|
|
6319
6420
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
6320
6421
|
}
|
|
6321
6422
|
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
6322
|
-
console.error(`${
|
|
6423
|
+
console.error(`${TAG19} waitForLogin poll timeout account=${accountId} elapsed=${elapsed}s \u2014 login kept alive (pairing code still valid)`);
|
|
6323
6424
|
return { connected: false, message: "Still waiting for you to enter the pairing code. The code is still valid \u2014 ask me to keep waiting, or request a new code.", configPersisted: false };
|
|
6324
6425
|
}
|
|
6325
6426
|
|
|
@@ -6434,7 +6535,7 @@ function serializeWhatsAppSchema() {
|
|
|
6434
6535
|
// app/lib/whatsapp/status-reconcile.ts
|
|
6435
6536
|
import { readdirSync as readdirSync2 } from "fs";
|
|
6436
6537
|
import { join as join5 } from "path";
|
|
6437
|
-
var
|
|
6538
|
+
var TAG20 = "[whatsapp:reconcile]";
|
|
6438
6539
|
function listCredsAccountIds(credsRoot) {
|
|
6439
6540
|
try {
|
|
6440
6541
|
return readdirSync2(credsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && hasCredsSync(join5(credsRoot, e.name))).map((e) => e.name);
|
|
@@ -6456,14 +6557,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6456
6557
|
const selfPhone = readSelfId(authDir).e164 ?? void 0;
|
|
6457
6558
|
const configured = accountDir ? isAccountConfigured(accountDir, accountId) : false;
|
|
6458
6559
|
console.error(
|
|
6459
|
-
`${
|
|
6560
|
+
`${TAG20} wa-link-unconfigured account=${accountId} creds=present config=${configured ? "present" : "absent"}`
|
|
6460
6561
|
);
|
|
6461
6562
|
if (!configured && accountDir) {
|
|
6462
6563
|
const result = persistAfterPairing(accountDir, accountId, selfPhone ?? null);
|
|
6463
6564
|
if (result.ok) {
|
|
6464
|
-
console.error(`${
|
|
6565
|
+
console.error(`${TAG20} self-healed account=${accountId}`);
|
|
6465
6566
|
} else {
|
|
6466
|
-
console.error(`${
|
|
6567
|
+
console.error(`${TAG20} self-heal FAILED account=${accountId}: ${result.error}`);
|
|
6467
6568
|
}
|
|
6468
6569
|
}
|
|
6469
6570
|
entries.push({ accountId, selfPhone, connected: false, linkedUnconfigured: !configured });
|
|
@@ -6472,7 +6573,7 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6472
6573
|
}
|
|
6473
6574
|
|
|
6474
6575
|
// server/routes/whatsapp.ts
|
|
6475
|
-
var
|
|
6576
|
+
var TAG21 = "[whatsapp:api]";
|
|
6476
6577
|
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
6477
6578
|
var app3 = new Hono();
|
|
6478
6579
|
app3.get("/status", (c) => {
|
|
@@ -6484,10 +6585,10 @@ app3.get("/status", (c) => {
|
|
|
6484
6585
|
const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds });
|
|
6485
6586
|
const accounts = [...live, ...reconciled];
|
|
6486
6587
|
const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}`).join(", ");
|
|
6487
|
-
console.error(`${
|
|
6588
|
+
console.error(`${TAG21} status accounts=${accounts.length} [${summary}]`);
|
|
6488
6589
|
return c.json({ accounts });
|
|
6489
6590
|
} catch (err) {
|
|
6490
|
-
console.error(`${
|
|
6591
|
+
console.error(`${TAG21} status error: ${String(err)}`);
|
|
6491
6592
|
return c.json({ error: String(err) }, 500);
|
|
6492
6593
|
}
|
|
6493
6594
|
});
|
|
@@ -6503,10 +6604,10 @@ app3.post("/login/start", async (c) => {
|
|
|
6503
6604
|
const authDir = join6(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
6504
6605
|
const accountDir = resolveAccount()?.accountDir ?? null;
|
|
6505
6606
|
const result = await startLogin({ accountId, authDir, phone, accountDir, force });
|
|
6506
|
-
console.error(`${
|
|
6607
|
+
console.error(`${TAG21} login/start result account=${accountId} hasCode=${!!result.pairingCode}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
6507
6608
|
return c.json(result);
|
|
6508
6609
|
} catch (err) {
|
|
6509
|
-
console.error(`${
|
|
6610
|
+
console.error(`${TAG21} login/start error: ${String(err)}`);
|
|
6510
6611
|
return c.json({ error: String(err) }, 500);
|
|
6511
6612
|
}
|
|
6512
6613
|
});
|
|
@@ -6516,7 +6617,7 @@ app3.post("/login/wait", async (c) => {
|
|
|
6516
6617
|
const accountId = validateAccountId(body.accountId);
|
|
6517
6618
|
const timeoutMs = body.timeoutMs ?? 6e4;
|
|
6518
6619
|
const result = await waitForLogin({ accountId, timeoutMs });
|
|
6519
|
-
console.error(`${
|
|
6620
|
+
console.error(`${TAG21} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
|
|
6520
6621
|
return c.json({
|
|
6521
6622
|
connected: result.connected,
|
|
6522
6623
|
message: result.message,
|
|
@@ -6524,7 +6625,7 @@ app3.post("/login/wait", async (c) => {
|
|
|
6524
6625
|
configPersisted: result.configPersisted
|
|
6525
6626
|
});
|
|
6526
6627
|
} catch (err) {
|
|
6527
|
-
console.error(`${
|
|
6628
|
+
console.error(`${TAG21} login/wait error: ${String(err)}`);
|
|
6528
6629
|
return c.json({ error: String(err) }, 500);
|
|
6529
6630
|
}
|
|
6530
6631
|
});
|
|
@@ -6535,7 +6636,7 @@ app3.post("/disconnect", async (c) => {
|
|
|
6535
6636
|
await stopConnection(accountId);
|
|
6536
6637
|
return c.json({ disconnected: true, accountId });
|
|
6537
6638
|
} catch (err) {
|
|
6538
|
-
console.error(`${
|
|
6639
|
+
console.error(`${TAG21} disconnect error: ${String(err)}`);
|
|
6539
6640
|
return c.json({ error: String(err) }, 500);
|
|
6540
6641
|
}
|
|
6541
6642
|
});
|
|
@@ -6546,7 +6647,7 @@ app3.post("/reconnect", async (c) => {
|
|
|
6546
6647
|
await startConnection(accountId);
|
|
6547
6648
|
return c.json({ reconnecting: true, accountId });
|
|
6548
6649
|
} catch (err) {
|
|
6549
|
-
console.error(`${
|
|
6650
|
+
console.error(`${TAG21} reconnect error: ${String(err)}`);
|
|
6550
6651
|
return c.json({ error: String(err) }, 500);
|
|
6551
6652
|
}
|
|
6552
6653
|
});
|
|
@@ -6567,7 +6668,7 @@ app3.post("/config", async (c) => {
|
|
|
6567
6668
|
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
6568
6669
|
}
|
|
6569
6670
|
const result = addAdminPhone(account.accountDir, phone);
|
|
6570
|
-
console.error(`${
|
|
6671
|
+
console.error(`${TAG21} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
|
|
6571
6672
|
return c.json(result, result.ok ? 200 : 400);
|
|
6572
6673
|
}
|
|
6573
6674
|
case "remove-admin-phone": {
|
|
@@ -6575,12 +6676,12 @@ app3.post("/config", async (c) => {
|
|
|
6575
6676
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
6576
6677
|
}
|
|
6577
6678
|
const result = removeAdminPhone(account.accountDir, phone);
|
|
6578
|
-
console.error(`${
|
|
6679
|
+
console.error(`${TAG21} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
|
|
6579
6680
|
return c.json(result, result.ok ? 200 : 400);
|
|
6580
6681
|
}
|
|
6581
6682
|
case "list-admin-phones": {
|
|
6582
6683
|
const phones = readAdminPhones(account.accountDir);
|
|
6583
|
-
console.error(`${
|
|
6684
|
+
console.error(`${TAG21} config action=list-admin-phones count=${phones.length}`);
|
|
6584
6685
|
return c.json({ ok: true, phones });
|
|
6585
6686
|
}
|
|
6586
6687
|
case "set-public-agent": {
|
|
@@ -6588,13 +6689,13 @@ app3.post("/config", async (c) => {
|
|
|
6588
6689
|
return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
|
|
6589
6690
|
}
|
|
6590
6691
|
const result = setPublicAgent(account.accountDir, slug);
|
|
6591
|
-
console.error(`${
|
|
6692
|
+
console.error(`${TAG21} config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
6592
6693
|
return c.json(result, result.ok ? 200 : 400);
|
|
6593
6694
|
}
|
|
6594
6695
|
case "get-public-agent": {
|
|
6595
6696
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
6596
6697
|
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount });
|
|
6597
|
-
console.error(`${
|
|
6698
|
+
console.error(`${TAG21} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
6598
6699
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
6599
6700
|
}
|
|
6600
6701
|
case "list-public-agents": {
|
|
@@ -6611,26 +6712,26 @@ app3.post("/config", async (c) => {
|
|
|
6611
6712
|
const config = JSON.parse(readFileSync7(configPath2, "utf-8"));
|
|
6612
6713
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
6613
6714
|
} catch {
|
|
6614
|
-
console.error(`${
|
|
6715
|
+
console.error(`${TAG21} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
6615
6716
|
}
|
|
6616
6717
|
}
|
|
6617
6718
|
} catch (err) {
|
|
6618
|
-
console.error(`${
|
|
6719
|
+
console.error(`${TAG21} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
|
|
6619
6720
|
}
|
|
6620
6721
|
}
|
|
6621
|
-
console.error(`${
|
|
6722
|
+
console.error(`${TAG21} config action=list-public-agents count=${agents.length}`);
|
|
6622
6723
|
return c.json({ ok: true, agents });
|
|
6623
6724
|
}
|
|
6624
6725
|
case "schema": {
|
|
6625
6726
|
const text = serializeWhatsAppSchema();
|
|
6626
|
-
console.error(`${
|
|
6727
|
+
console.error(`${TAG21} config action=schema`);
|
|
6627
6728
|
return c.json({ ok: true, text });
|
|
6628
6729
|
}
|
|
6629
6730
|
case "list-groups": {
|
|
6630
6731
|
const groupAccountId = accountId ?? "default";
|
|
6631
6732
|
const sock = getSocket(groupAccountId);
|
|
6632
6733
|
if (!sock) {
|
|
6633
|
-
console.error(`${
|
|
6734
|
+
console.error(`${TAG21} config action=list-groups error="not connected" accountId=${groupAccountId}`);
|
|
6634
6735
|
return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
|
|
6635
6736
|
}
|
|
6636
6737
|
try {
|
|
@@ -6640,10 +6741,10 @@ app3.post("/config", async (c) => {
|
|
|
6640
6741
|
name: g.subject ?? g.id,
|
|
6641
6742
|
participantCount: Array.isArray(g.participants) ? g.participants.length : 0
|
|
6642
6743
|
}));
|
|
6643
|
-
console.error(`${
|
|
6744
|
+
console.error(`${TAG21} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
|
|
6644
6745
|
return c.json({ ok: true, groups });
|
|
6645
6746
|
} catch (err) {
|
|
6646
|
-
console.error(`${
|
|
6747
|
+
console.error(`${TAG21} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
|
|
6647
6748
|
return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
|
|
6648
6749
|
}
|
|
6649
6750
|
}
|
|
@@ -6653,12 +6754,12 @@ app3.post("/config", async (c) => {
|
|
|
6653
6754
|
}
|
|
6654
6755
|
const result = updateConfig(account.accountDir, fields);
|
|
6655
6756
|
const fieldNames = Object.keys(fields);
|
|
6656
|
-
console.error(`${
|
|
6757
|
+
console.error(`${TAG21} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
|
|
6657
6758
|
return c.json(result, result.ok ? 200 : 400);
|
|
6658
6759
|
}
|
|
6659
6760
|
case "get-config": {
|
|
6660
6761
|
const waConfig = getConfig(account.accountDir);
|
|
6661
|
-
console.error(`${
|
|
6762
|
+
console.error(`${TAG21} config action=get-config`);
|
|
6662
6763
|
return c.json({ ok: true, config: waConfig });
|
|
6663
6764
|
}
|
|
6664
6765
|
default:
|
|
@@ -6668,7 +6769,7 @@ app3.post("/config", async (c) => {
|
|
|
6668
6769
|
);
|
|
6669
6770
|
}
|
|
6670
6771
|
} catch (err) {
|
|
6671
|
-
console.error(`${
|
|
6772
|
+
console.error(`${TAG21} config error: ${String(err)}`);
|
|
6672
6773
|
return c.json({ ok: false, error: String(err) }, 500);
|
|
6673
6774
|
}
|
|
6674
6775
|
});
|
|
@@ -6689,7 +6790,7 @@ app3.post("/send-document", async (c) => {
|
|
|
6689
6790
|
recordRouteDocumentOutbound(to, filePath);
|
|
6690
6791
|
return c.json({ success: true, messageId: result.messageId });
|
|
6691
6792
|
} catch (err) {
|
|
6692
|
-
console.error(`${
|
|
6793
|
+
console.error(`${TAG21} send-document error: ${String(err)}`);
|
|
6693
6794
|
return c.json({ error: String(err) }, 500);
|
|
6694
6795
|
}
|
|
6695
6796
|
});
|
|
@@ -6699,11 +6800,11 @@ app3.get("/activity", (c) => {
|
|
|
6699
6800
|
const result = getChannelActivity(accountId);
|
|
6700
6801
|
const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
|
|
6701
6802
|
console.error(
|
|
6702
|
-
`${
|
|
6803
|
+
`${TAG21} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
|
|
6703
6804
|
);
|
|
6704
6805
|
return c.json(result);
|
|
6705
6806
|
} catch (err) {
|
|
6706
|
-
console.error(`${
|
|
6807
|
+
console.error(`${TAG21} activity error: ${String(err)}`);
|
|
6707
6808
|
return c.json({ error: String(err) }, 500);
|
|
6708
6809
|
}
|
|
6709
6810
|
});
|
|
@@ -6722,10 +6823,10 @@ app3.get("/conversations", (c) => {
|
|
|
6722
6823
|
};
|
|
6723
6824
|
});
|
|
6724
6825
|
conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
|
|
6725
|
-
console.error(`${
|
|
6826
|
+
console.error(`${TAG21} conversations account=${accountId} count=${conversations.length}`);
|
|
6726
6827
|
return c.json({ conversations });
|
|
6727
6828
|
} catch (err) {
|
|
6728
|
-
console.error(`${
|
|
6829
|
+
console.error(`${TAG21} conversations error: ${String(err)}`);
|
|
6729
6830
|
return c.json({ error: String(err) }, 500);
|
|
6730
6831
|
}
|
|
6731
6832
|
});
|
|
@@ -6740,10 +6841,10 @@ app3.get("/messages", (c) => {
|
|
|
6740
6841
|
const limit = limitParam ? parseInt(limitParam, 10) : void 0;
|
|
6741
6842
|
const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
|
|
6742
6843
|
const messages = getMessages(accountId, jid, effectiveLimit);
|
|
6743
|
-
console.error(`${
|
|
6844
|
+
console.error(`${TAG21} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
|
|
6744
6845
|
return c.json({ messages });
|
|
6745
6846
|
} catch (err) {
|
|
6746
|
-
console.error(`${
|
|
6847
|
+
console.error(`${TAG21} messages error: ${String(err)}`);
|
|
6747
6848
|
return c.json({ error: String(err) }, 500);
|
|
6748
6849
|
}
|
|
6749
6850
|
});
|
|
@@ -6824,7 +6925,7 @@ app3.get("/conversation-graph-state", async (c) => {
|
|
|
6824
6925
|
}
|
|
6825
6926
|
} catch (err) {
|
|
6826
6927
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6827
|
-
console.error(`${
|
|
6928
|
+
console.error(`${TAG21} conversation-graph-state ERR cacheKey=${cacheKey} reason=${msg}`);
|
|
6828
6929
|
return c.json({ error: `Graph query failed: ${msg}`, cacheKey, cypher: cypher.trim() }, 500);
|
|
6829
6930
|
}
|
|
6830
6931
|
const ms = Date.now() - t0;
|
|
@@ -6837,7 +6938,7 @@ app3.get("/conversation-graph-state", async (c) => {
|
|
|
6837
6938
|
ms
|
|
6838
6939
|
});
|
|
6839
6940
|
} catch (err) {
|
|
6840
|
-
console.error(`${
|
|
6941
|
+
console.error(`${TAG21} conversation-graph-state error: ${String(err)}`);
|
|
6841
6942
|
return c.json({ error: String(err) }, 500);
|
|
6842
6943
|
}
|
|
6843
6944
|
});
|
|
@@ -6849,12 +6950,12 @@ app3.get("/group-info", async (c) => {
|
|
|
6849
6950
|
return c.json({ error: "Missing required parameter: jid" }, 400);
|
|
6850
6951
|
}
|
|
6851
6952
|
if (!isGroupJid(jid)) {
|
|
6852
|
-
console.error(`${
|
|
6953
|
+
console.error(`${TAG21} group-info error="not a group JID" jid=${jid} account=${accountId}`);
|
|
6853
6954
|
return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
|
|
6854
6955
|
}
|
|
6855
6956
|
const sock = getSocket(accountId);
|
|
6856
6957
|
if (!sock) {
|
|
6857
|
-
console.error(`${
|
|
6958
|
+
console.error(`${TAG21} group-info error="not connected" account=${accountId}`);
|
|
6858
6959
|
return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
|
|
6859
6960
|
}
|
|
6860
6961
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -6867,10 +6968,10 @@ app3.get("/group-info", async (c) => {
|
|
|
6867
6968
|
participantCount: meta.participants.length,
|
|
6868
6969
|
participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
|
|
6869
6970
|
};
|
|
6870
|
-
console.error(`${
|
|
6971
|
+
console.error(`${TAG21} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
|
|
6871
6972
|
return c.json(result);
|
|
6872
6973
|
} catch (err) {
|
|
6873
|
-
console.error(`${
|
|
6974
|
+
console.error(`${TAG21} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
|
|
6874
6975
|
return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
|
|
6875
6976
|
}
|
|
6876
6977
|
});
|
|
@@ -7248,6 +7349,7 @@ function resolveOperatorDisplay(userIdRes, nameRes) {
|
|
|
7248
7349
|
// app/lib/whatsapp-reader/parse-transcript.ts
|
|
7249
7350
|
var WA_REPLY_TOOL = "mcp__whatsapp-channel__reply";
|
|
7250
7351
|
var WA_REPLY_DOCUMENT_TOOL = "mcp__whatsapp-channel__reply-document";
|
|
7352
|
+
var WEBCHAT_REPLY_TOOL = "mcp__webchat-channel__reply";
|
|
7251
7353
|
var CLI_MARKER_PREFIXES2 = ["<local-command-", "<command-name>", "<command-message>"];
|
|
7252
7354
|
var CHANNEL_WRAPPER = /^<channel\b[^>]*>\n?([\s\S]*?)\n?<\/channel>$/;
|
|
7253
7355
|
function asString(content) {
|
|
@@ -7264,7 +7366,7 @@ function assistantTurns(content, ts) {
|
|
|
7264
7366
|
const b = block;
|
|
7265
7367
|
if (b.type === "text" && typeof b.text === "string") {
|
|
7266
7368
|
out.push({ kind: "agent-text", text: b.text, ts });
|
|
7267
|
-
} else if (b.type === "tool_use" && b.name === WA_REPLY_TOOL) {
|
|
7369
|
+
} else if (b.type === "tool_use" && (b.name === WA_REPLY_TOOL || b.name === WEBCHAT_REPLY_TOOL)) {
|
|
7268
7370
|
const text = b.input?.text;
|
|
7269
7371
|
out.push({ kind: "agent-reply", text: typeof text === "string" ? text : "", ts });
|
|
7270
7372
|
} else if (b.type === "tool_use" && b.name === WA_REPLY_DOCUMENT_TOOL) {
|
|
@@ -7600,9 +7702,57 @@ app5.get("/directive", requireAdminSession, (c) => {
|
|
|
7600
7702
|
});
|
|
7601
7703
|
var whatsapp_reader_default = app5;
|
|
7602
7704
|
|
|
7705
|
+
// server/routes/webchat.ts
|
|
7706
|
+
import { basename as basename3, dirname as dirname2 } from "path";
|
|
7707
|
+
import { join as join10 } from "path";
|
|
7708
|
+
var WEBCHAT_ADMIN_KEY = "webchat-admin";
|
|
7709
|
+
function createWebchatRoutes(deps) {
|
|
7710
|
+
const app48 = new Hono();
|
|
7711
|
+
app48.post("/send", requireAdminSession, async (c) => {
|
|
7712
|
+
const body = await c.req.json().catch(() => null);
|
|
7713
|
+
const text = body?.text;
|
|
7714
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
7715
|
+
return c.json({ error: "text required" }, 400);
|
|
7716
|
+
}
|
|
7717
|
+
let accountId;
|
|
7718
|
+
try {
|
|
7719
|
+
accountId = resolvePlatformAccountId();
|
|
7720
|
+
} catch (err) {
|
|
7721
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
7722
|
+
console.error(`[webchat:inbound] op=send-refused reason=account-unresolved detail=${detail}`);
|
|
7723
|
+
return c.json({ error: "account unresolved" }, 503);
|
|
7724
|
+
}
|
|
7725
|
+
await deps.handleInbound({ role: "admin", accountId, key: WEBCHAT_ADMIN_KEY, text });
|
|
7726
|
+
return c.json({ ok: true });
|
|
7727
|
+
});
|
|
7728
|
+
app48.get("/session", requireAdminSession, (c) => {
|
|
7729
|
+
let accountId;
|
|
7730
|
+
try {
|
|
7731
|
+
accountId = resolvePlatformAccountId();
|
|
7732
|
+
} catch (err) {
|
|
7733
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
7734
|
+
console.error(`[webchat:inbound] op=session-refused reason=account-unresolved detail=${detail}`);
|
|
7735
|
+
return c.json({ error: "account unresolved" }, 503);
|
|
7736
|
+
}
|
|
7737
|
+
const sessionId = adminSessionIdFor(accountId, WEBCHAT_ADMIN_KEY);
|
|
7738
|
+
const cfg = claudeConfigDir();
|
|
7739
|
+
let projectDir = null;
|
|
7740
|
+
if (cfg) {
|
|
7741
|
+
for (const { path: path2 } of enumerateJsonls(join10(cfg, "projects"))) {
|
|
7742
|
+
if (basename3(path2) === `${sessionId}.jsonl`) {
|
|
7743
|
+
projectDir = dirname2(path2);
|
|
7744
|
+
break;
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
return c.json({ sessionId, projectDir });
|
|
7749
|
+
});
|
|
7750
|
+
return app48;
|
|
7751
|
+
}
|
|
7752
|
+
|
|
7603
7753
|
// server/routes/telegram.ts
|
|
7604
7754
|
import { homedir } from "os";
|
|
7605
|
-
import { resolve as resolve10, join as
|
|
7755
|
+
import { resolve as resolve10, join as join11 } from "path";
|
|
7606
7756
|
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
7607
7757
|
|
|
7608
7758
|
// app/lib/telegram/access-control.ts
|
|
@@ -7650,11 +7800,11 @@ function routeTelegramUpdate(input) {
|
|
|
7650
7800
|
}
|
|
7651
7801
|
|
|
7652
7802
|
// server/routes/telegram.ts
|
|
7653
|
-
var
|
|
7803
|
+
var TAG22 = "[telegram-inbound]";
|
|
7654
7804
|
var DISPATCH_TIMEOUT_MS = 12e4;
|
|
7655
7805
|
function configDirName() {
|
|
7656
7806
|
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve10(process.cwd(), "..");
|
|
7657
|
-
const brandPath =
|
|
7807
|
+
const brandPath = join11(platformRoot2, "config", "brand.json");
|
|
7658
7808
|
if (existsSync8(brandPath)) {
|
|
7659
7809
|
try {
|
|
7660
7810
|
return JSON.parse(readFileSync11(brandPath, "utf-8")).configDir ?? ".maxy";
|
|
@@ -7685,18 +7835,18 @@ app6.post("/", async (c) => {
|
|
|
7685
7835
|
const botParam = c.req.query("bot");
|
|
7686
7836
|
const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
|
|
7687
7837
|
if (!botType) {
|
|
7688
|
-
console.error(`${
|
|
7838
|
+
console.error(`${TAG22} op=reject reason=missing-bot-param`);
|
|
7689
7839
|
return c.json({ ok: false }, 400);
|
|
7690
7840
|
}
|
|
7691
7841
|
const sp = secretPath(botType);
|
|
7692
7842
|
if (!existsSync8(sp)) {
|
|
7693
|
-
console.error(`${
|
|
7843
|
+
console.error(`${TAG22} op=reject reason=no-secret-file botType=${botType}`);
|
|
7694
7844
|
return c.json({ ok: false }, 401);
|
|
7695
7845
|
}
|
|
7696
7846
|
const expected = readFileSync11(sp, "utf-8").trim();
|
|
7697
7847
|
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
7698
7848
|
if (got !== expected) {
|
|
7699
|
-
console.error(`${
|
|
7849
|
+
console.error(`${TAG22} op=reject reason=bad-secret botType=${botType}`);
|
|
7700
7850
|
return c.json({ ok: false }, 401);
|
|
7701
7851
|
}
|
|
7702
7852
|
let update;
|
|
@@ -7707,25 +7857,25 @@ app6.post("/", async (c) => {
|
|
|
7707
7857
|
}
|
|
7708
7858
|
const account = resolveAccount();
|
|
7709
7859
|
if (!account) {
|
|
7710
|
-
console.error(`${
|
|
7860
|
+
console.error(`${TAG22} op=reject reason=no-account`);
|
|
7711
7861
|
return c.json({ ok: true }, 200);
|
|
7712
7862
|
}
|
|
7713
7863
|
const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
|
|
7714
7864
|
if (decision.kind === "ignore") {
|
|
7715
|
-
console.error(`${
|
|
7865
|
+
console.error(`${TAG22} op=ignore reason=${decision.reason}`);
|
|
7716
7866
|
return c.json({ ok: true }, 200);
|
|
7717
7867
|
}
|
|
7718
7868
|
if (decision.kind === "denied") {
|
|
7719
|
-
console.error(`${
|
|
7869
|
+
console.error(`${TAG22} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
|
|
7720
7870
|
return c.json({ ok: true }, 200);
|
|
7721
7871
|
}
|
|
7722
7872
|
const role = decision.agentType === "admin" ? "admin" : "public";
|
|
7723
7873
|
const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
|
|
7724
7874
|
if (!agentSlug) {
|
|
7725
|
-
console.error(`${
|
|
7875
|
+
console.error(`${TAG22} op=reject reason=no-default-agent`);
|
|
7726
7876
|
return c.json({ ok: true }, 200);
|
|
7727
7877
|
}
|
|
7728
|
-
console.error(`${
|
|
7878
|
+
console.error(`${TAG22} op=update accountId=${account.accountId} from=${decision.senderId}`);
|
|
7729
7879
|
const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
|
|
7730
7880
|
const { senderId, chatId, text } = decision;
|
|
7731
7881
|
void (async () => {
|
|
@@ -7739,16 +7889,16 @@ app6.post("/", async (c) => {
|
|
|
7739
7889
|
timeoutMs: DISPATCH_TIMEOUT_MS
|
|
7740
7890
|
});
|
|
7741
7891
|
if ("error" in result) {
|
|
7742
|
-
console.error(`${
|
|
7892
|
+
console.error(`${TAG22} op=dispatch-failed from=${senderId} error=${result.error}`);
|
|
7743
7893
|
return;
|
|
7744
7894
|
}
|
|
7745
7895
|
if (!botToken) {
|
|
7746
7896
|
const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
|
|
7747
|
-
console.error(`${
|
|
7897
|
+
console.error(`${TAG22} op=reply-dropped reason=${reason} botType=${botType}`);
|
|
7748
7898
|
return;
|
|
7749
7899
|
}
|
|
7750
7900
|
const sent = await sendTelegram(botToken, chatId, result.turnText);
|
|
7751
|
-
console.error(`${
|
|
7901
|
+
console.error(`${TAG22} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
|
|
7752
7902
|
})();
|
|
7753
7903
|
return c.json({ ok: true }, 200);
|
|
7754
7904
|
});
|
|
@@ -7761,7 +7911,7 @@ import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
|
|
|
7761
7911
|
|
|
7762
7912
|
// ../lib/admins-write/src/index.ts
|
|
7763
7913
|
import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync5, renameSync, mkdirSync as mkdirSync2, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
7764
|
-
import { dirname as
|
|
7914
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
7765
7915
|
function logLine(input, result) {
|
|
7766
7916
|
const userIdShort = input.userId.slice(0, 8);
|
|
7767
7917
|
console.error(
|
|
@@ -7769,7 +7919,7 @@ function logLine(input, result) {
|
|
|
7769
7919
|
);
|
|
7770
7920
|
}
|
|
7771
7921
|
function writeFileAtomic(filePath, contents, mode) {
|
|
7772
|
-
mkdirSync2(
|
|
7922
|
+
mkdirSync2(dirname3(filePath), { recursive: true });
|
|
7773
7923
|
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
7774
7924
|
writeFileSync5(tempPath, contents, mode !== void 0 ? { mode } : void 0);
|
|
7775
7925
|
renameSync(tempPath, filePath);
|
|
@@ -7803,7 +7953,7 @@ function writeAdminEntry(input) {
|
|
|
7803
7953
|
return result;
|
|
7804
7954
|
}
|
|
7805
7955
|
try {
|
|
7806
|
-
const accountJsonPath =
|
|
7956
|
+
const accountJsonPath = join12(input.accountDir, "account.json");
|
|
7807
7957
|
if (!existsSync9(accountJsonPath)) {
|
|
7808
7958
|
throw new Error(`account.json not found at ${accountJsonPath}`);
|
|
7809
7959
|
}
|
|
@@ -7858,13 +8008,13 @@ function checkAdminAuthInvariant(input) {
|
|
|
7858
8008
|
}
|
|
7859
8009
|
for (const entry of entries) {
|
|
7860
8010
|
if (entry.startsWith(".")) continue;
|
|
7861
|
-
const accountDir =
|
|
8011
|
+
const accountDir = join12(input.accountsDir, entry);
|
|
7862
8012
|
try {
|
|
7863
8013
|
if (!statSync5(accountDir).isDirectory()) continue;
|
|
7864
8014
|
} catch {
|
|
7865
8015
|
continue;
|
|
7866
8016
|
}
|
|
7867
|
-
const accountJsonPath =
|
|
8017
|
+
const accountJsonPath = join12(accountDir, "account.json");
|
|
7868
8018
|
if (!existsSync9(accountJsonPath)) continue;
|
|
7869
8019
|
let admins = [];
|
|
7870
8020
|
try {
|
|
@@ -7955,11 +8105,17 @@ app7.post("/claude-auth", async (c) => {
|
|
|
7955
8105
|
return c.json({ stopped: true });
|
|
7956
8106
|
}
|
|
7957
8107
|
if (body.action === "logout") {
|
|
8108
|
+
const start = Date.now();
|
|
8109
|
+
let logoutError = null;
|
|
7958
8110
|
try {
|
|
7959
8111
|
execFileSync2("claude", ["auth", "logout"], { encoding: "utf-8", timeout: 8e3 });
|
|
7960
|
-
} catch {
|
|
8112
|
+
} catch (err) {
|
|
8113
|
+
logoutError = err instanceof Error ? err.message : String(err);
|
|
7961
8114
|
}
|
|
7962
|
-
|
|
8115
|
+
const credentialsPresent = existsSync10(CLAUDE_CREDENTIALS_FILE);
|
|
8116
|
+
const ranMs = Date.now() - start;
|
|
8117
|
+
console.log(`[onboarding] op=claude-logout credentialsPresent=${credentialsPresent} ranMs=${ranMs} logoutError=${logoutError ? JSON.stringify(logoutError.slice(0, 120)) : "none"}`);
|
|
8118
|
+
return c.json({ logged_out: !credentialsPresent });
|
|
7963
8119
|
}
|
|
7964
8120
|
if (body.action === "wait") {
|
|
7965
8121
|
const authenticated = await checkAuthStatus();
|
|
@@ -8147,8 +8303,8 @@ var onboarding_default = app7;
|
|
|
8147
8303
|
|
|
8148
8304
|
// server/routes/client-error.ts
|
|
8149
8305
|
import { appendFileSync, existsSync as existsSync11, renameSync as renameSync2, statSync as statSync6 } from "fs";
|
|
8150
|
-
import { join as
|
|
8151
|
-
var CLIENT_ERRORS_LOG =
|
|
8306
|
+
import { join as join13 } from "path";
|
|
8307
|
+
var CLIENT_ERRORS_LOG = join13(LOG_DIR, "client-errors.log");
|
|
8152
8308
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8153
8309
|
var MAX_BODY_SIZE = 8 * 1024;
|
|
8154
8310
|
var MAX_STACK_LEN = 2e3;
|
|
@@ -8474,16 +8630,16 @@ var session_default = app9;
|
|
|
8474
8630
|
|
|
8475
8631
|
// server/routes/admin/logs.ts
|
|
8476
8632
|
import { existsSync as existsSync13, readdirSync as readdirSync8, readFileSync as readFileSync14, statSync as statSync7 } from "fs";
|
|
8477
|
-
import { resolve as resolve11, basename as
|
|
8633
|
+
import { resolve as resolve11, basename as basename4 } from "path";
|
|
8478
8634
|
|
|
8479
8635
|
// app/lib/logs-read-resolve.ts
|
|
8480
8636
|
import { existsSync as existsSync12 } from "fs";
|
|
8481
|
-
import { join as
|
|
8637
|
+
import { join as join14 } from "path";
|
|
8482
8638
|
function resolveSessionLogPaths(filename, logDirs) {
|
|
8483
8639
|
const tried = [filename];
|
|
8484
8640
|
const hits = [];
|
|
8485
8641
|
for (const dir of logDirs) {
|
|
8486
|
-
const fullPath =
|
|
8642
|
+
const fullPath = join14(dir, filename);
|
|
8487
8643
|
if (existsSync12(fullPath)) {
|
|
8488
8644
|
hits.push({ path: fullPath, dir });
|
|
8489
8645
|
}
|
|
@@ -8506,7 +8662,7 @@ app10.get("/", async (c) => {
|
|
|
8506
8662
|
if (accountLogDir) logDirs.push(accountLogDir);
|
|
8507
8663
|
logDirs.push(LOG_DIR);
|
|
8508
8664
|
if (fileParam) {
|
|
8509
|
-
const safe =
|
|
8665
|
+
const safe = basename4(fileParam);
|
|
8510
8666
|
const searched = [];
|
|
8511
8667
|
for (const dir of logDirs) {
|
|
8512
8668
|
const filePath = resolve11(dir, safe);
|
|
@@ -8576,7 +8732,7 @@ app10.get("/", async (c) => {
|
|
|
8576
8732
|
if (hit) {
|
|
8577
8733
|
console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
|
|
8578
8734
|
try {
|
|
8579
|
-
const filename =
|
|
8735
|
+
const filename = basename4(hit.path);
|
|
8580
8736
|
const buffer = readFileSync14(hit.path);
|
|
8581
8737
|
const onDiskBytes = statSync7(hit.path).size;
|
|
8582
8738
|
const headers = {
|
|
@@ -9485,7 +9641,7 @@ var sessions_default = app14;
|
|
|
9485
9641
|
|
|
9486
9642
|
// app/lib/claude-agent/spawn-context.ts
|
|
9487
9643
|
import { existsSync as existsSync17, readFileSync as readFileSync16, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
|
|
9488
|
-
import { dirname as
|
|
9644
|
+
import { dirname as dirname4, resolve as resolve14, join as join15 } from "path";
|
|
9489
9645
|
async function resolveOwnerProfileBlock(accountId, userId) {
|
|
9490
9646
|
if (!userId) return { ok: false, reason: "missing-user-id" };
|
|
9491
9647
|
try {
|
|
@@ -9514,8 +9670,8 @@ function frontmatterField(manifest, field) {
|
|
|
9514
9670
|
function extractPluginToolDescriptions(pluginDir) {
|
|
9515
9671
|
const out = /* @__PURE__ */ new Map();
|
|
9516
9672
|
const candidates = [
|
|
9517
|
-
|
|
9518
|
-
|
|
9673
|
+
join15(pluginDir, "mcp/dist/index.js"),
|
|
9674
|
+
join15(pluginDir, "mcp/src/index.ts")
|
|
9519
9675
|
];
|
|
9520
9676
|
let raw = null;
|
|
9521
9677
|
for (const path2 of candidates) {
|
|
@@ -9573,21 +9729,21 @@ function listPluginDirs() {
|
|
|
9573
9729
|
const platformPlugins = resolve14(PLATFORM_ROOT, "plugins");
|
|
9574
9730
|
if (existsSync17(platformPlugins)) {
|
|
9575
9731
|
for (const name of readdirSync10(platformPlugins)) {
|
|
9576
|
-
const dir =
|
|
9732
|
+
const dir = join15(platformPlugins, name);
|
|
9577
9733
|
try {
|
|
9578
9734
|
if (statSync8(dir).isDirectory()) out.set(name, dir);
|
|
9579
9735
|
} catch {
|
|
9580
9736
|
}
|
|
9581
9737
|
}
|
|
9582
9738
|
}
|
|
9583
|
-
const premiumRoot = resolve14(
|
|
9739
|
+
const premiumRoot = resolve14(dirname4(PLATFORM_ROOT), "premium-plugins");
|
|
9584
9740
|
if (existsSync17(premiumRoot)) {
|
|
9585
9741
|
for (const bundle of readdirSync10(premiumRoot)) {
|
|
9586
|
-
const bundlePlugins =
|
|
9742
|
+
const bundlePlugins = join15(premiumRoot, bundle, "plugins");
|
|
9587
9743
|
if (!existsSync17(bundlePlugins)) continue;
|
|
9588
9744
|
try {
|
|
9589
9745
|
for (const name of readdirSync10(bundlePlugins)) {
|
|
9590
|
-
const dir =
|
|
9746
|
+
const dir = join15(bundlePlugins, name);
|
|
9591
9747
|
try {
|
|
9592
9748
|
if (statSync8(dir).isDirectory()) out.set(name, dir);
|
|
9593
9749
|
} catch {
|
|
@@ -9630,7 +9786,7 @@ function computeActivePlugins(accountId) {
|
|
|
9630
9786
|
for (const name of Array.from(enabled).sort()) {
|
|
9631
9787
|
const dir = pluginDirs.get(name);
|
|
9632
9788
|
if (!dir) continue;
|
|
9633
|
-
const fm = readFrontmatter(
|
|
9789
|
+
const fm = readFrontmatter(join15(dir, "PLUGIN.md"));
|
|
9634
9790
|
if (!fm) continue;
|
|
9635
9791
|
const description = frontmatterField(`---
|
|
9636
9792
|
${fm}
|
|
@@ -9647,7 +9803,7 @@ ${fm}
|
|
|
9647
9803
|
`plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
|
|
9648
9804
|
);
|
|
9649
9805
|
if (toolNames.length > 0 && descMap.size === 0) {
|
|
9650
|
-
const hasSource = existsSync17(
|
|
9806
|
+
const hasSource = existsSync17(join15(dir, "mcp/dist/index.js")) || existsSync17(join15(dir, "mcp/src/index.ts"));
|
|
9651
9807
|
if (hasSource) {
|
|
9652
9808
|
console.warn(
|
|
9653
9809
|
`[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
|
|
@@ -9657,7 +9813,7 @@ ${fm}
|
|
|
9657
9813
|
const skillPaths = parseSkillPathsFromFrontmatter(fm);
|
|
9658
9814
|
const skills = [];
|
|
9659
9815
|
for (const relPath of skillPaths) {
|
|
9660
|
-
const skillPath =
|
|
9816
|
+
const skillPath = join15(dir, relPath);
|
|
9661
9817
|
const skillFm = readFrontmatter(skillPath);
|
|
9662
9818
|
if (!skillFm) continue;
|
|
9663
9819
|
const skillName = frontmatterField(`---
|
|
@@ -9701,7 +9857,7 @@ function computeSpecialistDomains(accountId) {
|
|
|
9701
9857
|
const out = [];
|
|
9702
9858
|
for (const file of entries.sort()) {
|
|
9703
9859
|
if (!file.endsWith(".md")) continue;
|
|
9704
|
-
const fm = readFrontmatter(
|
|
9860
|
+
const fm = readFrontmatter(join15(specialistsDir, file));
|
|
9705
9861
|
if (!fm) continue;
|
|
9706
9862
|
const wrapped = `---
|
|
9707
9863
|
${fm}
|
|
@@ -9794,7 +9950,7 @@ function resolveTunnelUrl() {
|
|
|
9794
9950
|
if (!state) return null;
|
|
9795
9951
|
return `https://${hostname2}.${state.domain}`;
|
|
9796
9952
|
}
|
|
9797
|
-
var
|
|
9953
|
+
var TAG23 = "[claude-session-manager:wrapper]";
|
|
9798
9954
|
async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
9799
9955
|
const auth = await ensureAuth();
|
|
9800
9956
|
if (auth.status !== "dead" && auth.status !== "missing") return null;
|
|
@@ -9812,12 +9968,12 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
9812
9968
|
const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
|
|
9813
9969
|
const ownerMs = Date.now() - ownerStart;
|
|
9814
9970
|
const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
|
|
9815
|
-
console.log(`${
|
|
9971
|
+
console.log(`${TAG23} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
|
|
9816
9972
|
const dormantPlugins = computeDormantPlugins(args.senderId);
|
|
9817
9973
|
const activePlugins = computeActivePlugins(args.senderId);
|
|
9818
9974
|
const specialistDomains = computeSpecialistDomains(args.senderId);
|
|
9819
9975
|
const tunnelUrl = resolveTunnelUrl();
|
|
9820
|
-
console.log(`${
|
|
9976
|
+
console.log(`${TAG23} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
9821
9977
|
const upstreamPayload = JSON.stringify({
|
|
9822
9978
|
senderId: args.senderId,
|
|
9823
9979
|
// Task 205 — pass userId through to the manager so it lands as
|
|
@@ -9844,24 +10000,24 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
9844
10000
|
// unshapely values.
|
|
9845
10001
|
conversationNodeId: args.conversationNodeId
|
|
9846
10002
|
});
|
|
9847
|
-
console.log(`${
|
|
10003
|
+
console.log(`${TAG23} forward-spawn-start managerBase=${managerBase3("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
|
|
9848
10004
|
const forwardStart = Date.now();
|
|
9849
10005
|
const upstream = await fetch(`${managerBase3("claude-session-manager:wrapper")}/public-spawn`, {
|
|
9850
10006
|
method: "POST",
|
|
9851
10007
|
headers: { "content-type": "application/json" },
|
|
9852
10008
|
body: upstreamPayload
|
|
9853
10009
|
}).catch((err) => {
|
|
9854
|
-
console.error(`${
|
|
10010
|
+
console.error(`${TAG23} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
|
|
9855
10011
|
return null;
|
|
9856
10012
|
});
|
|
9857
10013
|
if (!upstream) return {
|
|
9858
10014
|
response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
|
|
9859
10015
|
claudeSessionId: null
|
|
9860
10016
|
};
|
|
9861
|
-
console.log(`${
|
|
10017
|
+
console.log(`${TAG23} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
|
|
9862
10018
|
if (args.initialMessage) {
|
|
9863
10019
|
const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
|
|
9864
|
-
console.log(`${
|
|
10020
|
+
console.log(`${TAG23} initial-message-inlined bytes=${inputBytes}`);
|
|
9865
10021
|
}
|
|
9866
10022
|
const bodyText = await upstream.text().catch(() => "");
|
|
9867
10023
|
let claudeSessionId = null;
|
|
@@ -9896,7 +10052,7 @@ app15.post("/", async (c) => {
|
|
|
9896
10052
|
if (refusal) return refusal;
|
|
9897
10053
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
9898
10054
|
if (!senderId) {
|
|
9899
|
-
console.error(`${
|
|
10055
|
+
console.error(`${TAG23} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
|
|
9900
10056
|
return c.json({ error: "admin-account-not-resolved" }, 500);
|
|
9901
10057
|
}
|
|
9902
10058
|
const userId = getUserIdForSession(cacheKey) ?? void 0;
|
|
@@ -9905,7 +10061,7 @@ app15.post("/", async (c) => {
|
|
|
9905
10061
|
const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
|
|
9906
10062
|
const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
|
|
9907
10063
|
const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
|
|
9908
|
-
console.log(`${
|
|
10064
|
+
console.log(`${TAG23} spawn-request-in surface=cookie accountId=${senderId.slice(0, 8)} userId=${userId ? userId.slice(0, 8) : "absent"} channel=${channel} permissionMode=${permissionMode ?? "default"} specialist=${specialist ?? "none"} model=${model ?? "default"} initialMessage=${initialMessage ? "yes" : "no"}`);
|
|
9909
10065
|
const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
|
|
9910
10066
|
const { response, claudeSessionId } = await performSpawnWithInitialMessage({
|
|
9911
10067
|
senderId,
|
|
@@ -9924,13 +10080,13 @@ app15.post("/", async (c) => {
|
|
|
9924
10080
|
claudeSessionId,
|
|
9925
10081
|
senderId
|
|
9926
10082
|
);
|
|
9927
|
-
console.log(`${
|
|
10083
|
+
console.log(`${TAG23} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
|
|
9928
10084
|
return response;
|
|
9929
10085
|
});
|
|
9930
10086
|
var claude_sessions_default = app15;
|
|
9931
10087
|
|
|
9932
10088
|
// server/routes/admin/log-ingest.ts
|
|
9933
|
-
var
|
|
10089
|
+
var TAG24 = "[log-ingest]";
|
|
9934
10090
|
var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
|
|
9935
10091
|
var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
9936
10092
|
var MAX_LINE_BYTES = 4096;
|
|
@@ -9941,7 +10097,7 @@ function isLoopbackAddr(addr) {
|
|
|
9941
10097
|
app16.post("/", async (c) => {
|
|
9942
10098
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
9943
10099
|
if (!isLoopbackAddr(remoteAddr)) {
|
|
9944
|
-
console.error(`${
|
|
10100
|
+
console.error(`${TAG24} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
9945
10101
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
9946
10102
|
}
|
|
9947
10103
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -9950,7 +10106,7 @@ app16.post("/", async (c) => {
|
|
|
9950
10106
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
9951
10107
|
const offender = tokens.find((t) => !isLoopbackAddr(t));
|
|
9952
10108
|
if (offender !== void 0) {
|
|
9953
|
-
console.error(`${
|
|
10109
|
+
console.error(`${TAG24} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
9954
10110
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
9955
10111
|
}
|
|
9956
10112
|
}
|
|
@@ -9992,18 +10148,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
9992
10148
|
]);
|
|
9993
10149
|
var app17 = new Hono();
|
|
9994
10150
|
app17.post("/", async (c) => {
|
|
9995
|
-
const
|
|
10151
|
+
const TAG32 = "[admin:events]";
|
|
9996
10152
|
let body;
|
|
9997
10153
|
try {
|
|
9998
10154
|
body = await c.req.json();
|
|
9999
10155
|
} catch (err) {
|
|
10000
10156
|
const detail = err instanceof Error ? err.message : String(err);
|
|
10001
|
-
console.error(`${
|
|
10157
|
+
console.error(`${TAG32} reject reason=body-not-json detail=${detail}`);
|
|
10002
10158
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
10003
10159
|
}
|
|
10004
10160
|
const event = typeof body.event === "string" ? body.event : "";
|
|
10005
10161
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
10006
|
-
console.error(`${
|
|
10162
|
+
console.error(`${TAG32} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
10007
10163
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
10008
10164
|
}
|
|
10009
10165
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -10028,7 +10184,7 @@ var events_default = app17;
|
|
|
10028
10184
|
import { createReadStream as createReadStream2 } from "fs";
|
|
10029
10185
|
import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
10030
10186
|
import { realpathSync as realpathSync4 } from "fs";
|
|
10031
|
-
import { basename as
|
|
10187
|
+
import { basename as basename6, dirname as dirname5, join as join17, resolve as resolve18, sep as sep5 } from "path";
|
|
10032
10188
|
import { Readable as Readable2 } from "stream";
|
|
10033
10189
|
|
|
10034
10190
|
// app/lib/data-path.ts
|
|
@@ -10408,7 +10564,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
10408
10564
|
|
|
10409
10565
|
// app/lib/file-index.ts
|
|
10410
10566
|
import * as fsp from "fs/promises";
|
|
10411
|
-
import { resolve as resolve17, relative as relative2, join as
|
|
10567
|
+
import { resolve as resolve17, relative as relative2, join as join16, basename as basename5, extname as extname2, sep as sep4 } from "path";
|
|
10412
10568
|
import { tmpdir as tmpdir2 } from "os";
|
|
10413
10569
|
import { execFile as execFile2 } from "child_process";
|
|
10414
10570
|
import { promisify as promisify2 } from "util";
|
|
@@ -10479,7 +10635,7 @@ async function extractPdf(absolute) {
|
|
|
10479
10635
|
return stdout.trim();
|
|
10480
10636
|
}
|
|
10481
10637
|
async function ocrPdf(absolute) {
|
|
10482
|
-
const outPdf =
|
|
10638
|
+
const outPdf = join16(tmpdir2(), `file-index-ocr-${process.pid}-${Date.now()}.pdf`);
|
|
10483
10639
|
try {
|
|
10484
10640
|
await execFileAsync2(
|
|
10485
10641
|
"ocrmypdf",
|
|
@@ -10535,13 +10691,13 @@ async function extractFileContent(absolute) {
|
|
|
10535
10691
|
}
|
|
10536
10692
|
}
|
|
10537
10693
|
async function readDisplayName(absolute) {
|
|
10538
|
-
const dir = absolute.slice(0, absolute.length -
|
|
10539
|
-
const base =
|
|
10694
|
+
const dir = absolute.slice(0, absolute.length - basename5(absolute).length);
|
|
10695
|
+
const base = basename5(absolute);
|
|
10540
10696
|
const dot = base.lastIndexOf(".");
|
|
10541
10697
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
10542
10698
|
if (base === `${stem}.meta.json`) return null;
|
|
10543
10699
|
try {
|
|
10544
|
-
const raw = await fsp.readFile(
|
|
10700
|
+
const raw = await fsp.readFile(join16(dir, `${stem}.meta.json`), "utf-8");
|
|
10545
10701
|
const meta = JSON.parse(raw);
|
|
10546
10702
|
const dn = meta.displayName ?? meta.filename;
|
|
10547
10703
|
return typeof dn === "string" && dn.length > 0 ? dn : null;
|
|
@@ -10561,7 +10717,7 @@ async function walkSubtree(root, dataRoot, out) {
|
|
|
10561
10717
|
let clean = true;
|
|
10562
10718
|
for (const entry of entries) {
|
|
10563
10719
|
if (entry.isSymbolicLink()) continue;
|
|
10564
|
-
const abs =
|
|
10720
|
+
const abs = join16(root, entry.name);
|
|
10565
10721
|
if (entry.isDirectory()) {
|
|
10566
10722
|
const sub = await walkSubtree(abs, dataRoot, out);
|
|
10567
10723
|
if (!sub.clean) clean = false;
|
|
@@ -10673,7 +10829,7 @@ async function cascadeTrashLinkedKnowledgeDocs(session, accountId, relativePaths
|
|
|
10673
10829
|
return { kds, sections, chunks };
|
|
10674
10830
|
}
|
|
10675
10831
|
async function buildArtifact(accountId, wf, embed2) {
|
|
10676
|
-
const name =
|
|
10832
|
+
const name = basename5(wf.absolute);
|
|
10677
10833
|
const { content, route } = await extractFileContent(wf.absolute);
|
|
10678
10834
|
const displayName = await readDisplayName(wf.absolute);
|
|
10679
10835
|
const embedInput = `${name}
|
|
@@ -10832,7 +10988,7 @@ async function dropFileIndex(accountId, relativePath, opts) {
|
|
|
10832
10988
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10833
10989
|
async function readMeta(absDir, baseName) {
|
|
10834
10990
|
try {
|
|
10835
|
-
const raw = await readFile4(
|
|
10991
|
+
const raw = await readFile4(join17(absDir, `${baseName}.meta.json`), "utf8");
|
|
10836
10992
|
const parsed = JSON.parse(raw);
|
|
10837
10993
|
if (typeof parsed?.filename === "string") {
|
|
10838
10994
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -10870,7 +11026,7 @@ async function readAccountNames() {
|
|
|
10870
11026
|
}
|
|
10871
11027
|
async function enrich(absolute, entry, accountNames) {
|
|
10872
11028
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
10873
|
-
const meta = await readMeta(
|
|
11029
|
+
const meta = await readMeta(join17(absolute, entry.name), entry.name);
|
|
10874
11030
|
if (meta?.filename) {
|
|
10875
11031
|
entry.displayName = meta.filename;
|
|
10876
11032
|
entry.mimeType = meta.mimeType;
|
|
@@ -10968,7 +11124,7 @@ app18.get("/", requireAdminSession, async (c) => {
|
|
|
10968
11124
|
continue;
|
|
10969
11125
|
}
|
|
10970
11126
|
try {
|
|
10971
|
-
const entryPath =
|
|
11127
|
+
const entryPath = join17(absolute, name);
|
|
10972
11128
|
const s = await stat4(entryPath);
|
|
10973
11129
|
entries.push({
|
|
10974
11130
|
name,
|
|
@@ -11034,7 +11190,7 @@ app18.get("/download", requireAdminSession, async (c) => {
|
|
|
11034
11190
|
if (!info.isFile()) {
|
|
11035
11191
|
return c.json({ error: "Path is not a file" }, 400);
|
|
11036
11192
|
}
|
|
11037
|
-
const filename =
|
|
11193
|
+
const filename = basename6(absolute);
|
|
11038
11194
|
const mimeType = detectMimeType(absolute);
|
|
11039
11195
|
const nodeStream = createReadStream2(absolute);
|
|
11040
11196
|
const webStream = Readable2.toWeb(nodeStream);
|
|
@@ -11091,7 +11247,7 @@ app18.post("/upload", requireAdminSession, async (c) => {
|
|
|
11091
11247
|
error: `Unsupported file type: "${file.type}". Supported: ${[...SUPPORTED_MIME_TYPES].join(", ")}.`
|
|
11092
11248
|
}, 422);
|
|
11093
11249
|
}
|
|
11094
|
-
const safeName =
|
|
11250
|
+
const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
|
|
11095
11251
|
const finalName = `${Date.now()}-${safeName}`;
|
|
11096
11252
|
const destDir = resolve18(DATA_ROOT, "uploads", accountId);
|
|
11097
11253
|
const destPath = resolve18(destDir, finalName);
|
|
@@ -11143,7 +11299,7 @@ app18.delete("/", requireAdminSession, async (c) => {
|
|
|
11143
11299
|
console.error(`[data] account-scope-blocked endpoint="DELETE /api/admin/files" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
|
|
11144
11300
|
return c.json({ error: "Not found" }, 404);
|
|
11145
11301
|
}
|
|
11146
|
-
const base =
|
|
11302
|
+
const base = basename6(absolute);
|
|
11147
11303
|
const segments = relPath.split("/").filter(Boolean);
|
|
11148
11304
|
if (base === "account.json" || segments.includes(".git")) {
|
|
11149
11305
|
console.error(`[data] file-delete blocked path="${relPath}" reason="protected"`);
|
|
@@ -11159,7 +11315,7 @@ app18.delete("/", requireAdminSession, async (c) => {
|
|
|
11159
11315
|
}
|
|
11160
11316
|
const dot = base.lastIndexOf(".");
|
|
11161
11317
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
11162
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
11318
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join17(dirname5(absolute), `${stem}.meta.json`) : null;
|
|
11163
11319
|
await unlink2(absolute);
|
|
11164
11320
|
if (sidecarPath) {
|
|
11165
11321
|
try {
|
|
@@ -12135,6 +12291,12 @@ var FILTER_TOP_LEVEL_LABELS = Object.freeze(
|
|
|
12135
12291
|
"Property",
|
|
12136
12292
|
"Viewing",
|
|
12137
12293
|
"Offer",
|
|
12294
|
+
"Job",
|
|
12295
|
+
"QuoteDocument",
|
|
12296
|
+
"InboundInvoice",
|
|
12297
|
+
"SubContractor",
|
|
12298
|
+
"SubInvoice",
|
|
12299
|
+
"WhatsAppGroup",
|
|
12138
12300
|
"AdminConversation",
|
|
12139
12301
|
"PublicConversation",
|
|
12140
12302
|
"Task",
|
|
@@ -13202,7 +13364,7 @@ var graph_default_view_default = app24;
|
|
|
13202
13364
|
|
|
13203
13365
|
// server/routes/admin/sidebar-artefacts.ts
|
|
13204
13366
|
import { readdir as readdir4, stat as stat5 } from "fs/promises";
|
|
13205
|
-
import { resolve as resolve19, relative as relative3, isAbsolute, sep as sep6, basename as
|
|
13367
|
+
import { resolve as resolve19, relative as relative3, isAbsolute, sep as sep6, basename as basename7 } from "path";
|
|
13206
13368
|
import { existsSync as existsSync19 } from "fs";
|
|
13207
13369
|
var LIMIT = 50;
|
|
13208
13370
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
@@ -13254,7 +13416,7 @@ async function fetchAccountFileArtefacts(accountId) {
|
|
|
13254
13416
|
const modifiedAt = r.get("modifiedAt") ?? "";
|
|
13255
13417
|
return {
|
|
13256
13418
|
id: `account-file:${relativePath}`,
|
|
13257
|
-
name: displayName ??
|
|
13419
|
+
name: displayName ?? basename7(relativePath),
|
|
13258
13420
|
kind: "account-file",
|
|
13259
13421
|
updatedAt: modifiedAt,
|
|
13260
13422
|
mimeType,
|
|
@@ -13660,10 +13822,10 @@ var system_stats_default = app30;
|
|
|
13660
13822
|
|
|
13661
13823
|
// server/routes/admin/health.ts
|
|
13662
13824
|
import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
|
|
13663
|
-
import { resolve as resolve20, join as
|
|
13825
|
+
import { resolve as resolve20, join as join18 } from "path";
|
|
13664
13826
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
|
|
13665
13827
|
var brandHostname = "maxy";
|
|
13666
|
-
var brandJsonPath =
|
|
13828
|
+
var brandJsonPath = join18(PLATFORM_ROOT6, "config", "brand.json");
|
|
13667
13829
|
if (existsSync20(brandJsonPath)) {
|
|
13668
13830
|
try {
|
|
13669
13831
|
const brand = JSON.parse(readFileSync18(brandJsonPath, "utf-8"));
|
|
@@ -13728,7 +13890,7 @@ var health_default2 = app31;
|
|
|
13728
13890
|
|
|
13729
13891
|
// server/routes/admin/linkedin-ingest.ts
|
|
13730
13892
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
13731
|
-
var
|
|
13893
|
+
var TAG25 = "[linkedin-ingest-route]";
|
|
13732
13894
|
var UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
13733
13895
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
13734
13896
|
var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
|
|
@@ -13832,29 +13994,29 @@ app32.post("/", requireAdminSession, async (c) => {
|
|
|
13832
13994
|
try {
|
|
13833
13995
|
body = await c.req.json();
|
|
13834
13996
|
} catch {
|
|
13835
|
-
console.error(
|
|
13997
|
+
console.error(TAG25 + " rejected status=400 reason=schema:body-not-json");
|
|
13836
13998
|
return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
|
|
13837
13999
|
}
|
|
13838
14000
|
const v = validate(body);
|
|
13839
14001
|
if (!v.ok) {
|
|
13840
|
-
console.error(
|
|
14002
|
+
console.error(TAG25 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
|
|
13841
14003
|
return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
|
|
13842
14004
|
}
|
|
13843
14005
|
const envelope = v.envelope;
|
|
13844
14006
|
const cacheKey = c.var.cacheKey ?? "";
|
|
13845
14007
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
13846
14008
|
if (!senderId) {
|
|
13847
|
-
console.error(
|
|
14009
|
+
console.error(TAG25 + " rejected status=500 reason=admin-account-not-resolved");
|
|
13848
14010
|
return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
|
|
13849
14011
|
}
|
|
13850
14012
|
const payloadBytes = JSON.stringify(envelope).length;
|
|
13851
14013
|
console.log(
|
|
13852
|
-
|
|
14014
|
+
TAG25 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
|
|
13853
14015
|
);
|
|
13854
14016
|
const initialMessage = buildInitialMessage(envelope);
|
|
13855
14017
|
const spawnStart = Date.now();
|
|
13856
14018
|
const sessionId = randomUUID9();
|
|
13857
|
-
console.log(
|
|
14019
|
+
console.log(TAG25 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
13858
14020
|
const spawned = await managerRcSpawn({
|
|
13859
14021
|
sessionId,
|
|
13860
14022
|
initialMessage,
|
|
@@ -13863,12 +14025,12 @@ app32.post("/", requireAdminSession, async (c) => {
|
|
|
13863
14025
|
});
|
|
13864
14026
|
if ("error" in spawned) {
|
|
13865
14027
|
console.error(
|
|
13866
|
-
|
|
14028
|
+
TAG25 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
|
|
13867
14029
|
);
|
|
13868
14030
|
return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
|
|
13869
14031
|
}
|
|
13870
14032
|
console.log(
|
|
13871
|
-
|
|
14033
|
+
TAG25 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
|
|
13872
14034
|
);
|
|
13873
14035
|
return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
|
|
13874
14036
|
});
|
|
@@ -13876,7 +14038,7 @@ var linkedin_ingest_default = app32;
|
|
|
13876
14038
|
|
|
13877
14039
|
// server/routes/admin/post-turn-context.ts
|
|
13878
14040
|
import neo4j2 from "neo4j-driver";
|
|
13879
|
-
var
|
|
14041
|
+
var TAG26 = "[post-turn-context]";
|
|
13880
14042
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
13881
14043
|
"embedding",
|
|
13882
14044
|
"passwordHash",
|
|
@@ -13918,7 +14080,7 @@ var app33 = new Hono();
|
|
|
13918
14080
|
app33.get("/", async (c) => {
|
|
13919
14081
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
13920
14082
|
if (!isLoopbackAddr2(remoteAddr)) {
|
|
13921
|
-
console.error(`${
|
|
14083
|
+
console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
13922
14084
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
13923
14085
|
}
|
|
13924
14086
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -13927,7 +14089,7 @@ app33.get("/", async (c) => {
|
|
|
13927
14089
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
13928
14090
|
const offender = tokens.find((t) => !isLoopbackAddr2(t));
|
|
13929
14091
|
if (offender !== void 0) {
|
|
13930
|
-
console.error(`${
|
|
14092
|
+
console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
13931
14093
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
13932
14094
|
}
|
|
13933
14095
|
}
|
|
@@ -13959,7 +14121,7 @@ app33.get("/", async (c) => {
|
|
|
13959
14121
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
13960
14122
|
const total = Date.now() - started;
|
|
13961
14123
|
console.log(
|
|
13962
|
-
`${
|
|
14124
|
+
`${TAG26} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
|
|
13963
14125
|
);
|
|
13964
14126
|
return c.json({
|
|
13965
14127
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -13968,7 +14130,7 @@ app33.get("/", async (c) => {
|
|
|
13968
14130
|
const elapsed = Date.now() - started;
|
|
13969
14131
|
const message = err instanceof Error ? err.message : String(err);
|
|
13970
14132
|
console.error(
|
|
13971
|
-
`${
|
|
14133
|
+
`${TAG26} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
|
|
13972
14134
|
);
|
|
13973
14135
|
return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
|
|
13974
14136
|
} finally {
|
|
@@ -13979,7 +14141,7 @@ var post_turn_context_default = app33;
|
|
|
13979
14141
|
|
|
13980
14142
|
// server/routes/admin/public-session-context.ts
|
|
13981
14143
|
import neo4j3 from "neo4j-driver";
|
|
13982
|
-
var
|
|
14144
|
+
var TAG27 = "[public-session-context]";
|
|
13983
14145
|
var STRIPPED_PROPERTIES3 = /* @__PURE__ */ new Set([
|
|
13984
14146
|
"embedding",
|
|
13985
14147
|
"passwordHash",
|
|
@@ -14021,7 +14183,7 @@ var app34 = new Hono();
|
|
|
14021
14183
|
app34.get("/", async (c) => {
|
|
14022
14184
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
14023
14185
|
if (!isLoopbackAddr3(remoteAddr)) {
|
|
14024
|
-
console.error(`${
|
|
14186
|
+
console.error(`${TAG27} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
14025
14187
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
14026
14188
|
}
|
|
14027
14189
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -14030,7 +14192,7 @@ app34.get("/", async (c) => {
|
|
|
14030
14192
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
14031
14193
|
const offender = tokens.find((t) => !isLoopbackAddr3(t));
|
|
14032
14194
|
if (offender !== void 0) {
|
|
14033
|
-
console.error(`${
|
|
14195
|
+
console.error(`${TAG27} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
14034
14196
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
14035
14197
|
}
|
|
14036
14198
|
}
|
|
@@ -14061,7 +14223,7 @@ app34.get("/", async (c) => {
|
|
|
14061
14223
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
14062
14224
|
const total = Date.now() - started;
|
|
14063
14225
|
console.log(
|
|
14064
|
-
`${
|
|
14226
|
+
`${TAG27} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
|
|
14065
14227
|
);
|
|
14066
14228
|
return c.json({
|
|
14067
14229
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -14070,7 +14232,7 @@ app34.get("/", async (c) => {
|
|
|
14070
14232
|
const elapsed = Date.now() - started;
|
|
14071
14233
|
const message = err instanceof Error ? err.message : String(err);
|
|
14072
14234
|
console.error(
|
|
14073
|
-
`${
|
|
14235
|
+
`${TAG27} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
|
|
14074
14236
|
);
|
|
14075
14237
|
return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
|
|
14076
14238
|
} finally {
|
|
@@ -14080,7 +14242,7 @@ app34.get("/", async (c) => {
|
|
|
14080
14242
|
var public_session_context_default = app34;
|
|
14081
14243
|
|
|
14082
14244
|
// server/routes/admin/public-session-exit.ts
|
|
14083
|
-
var
|
|
14245
|
+
var TAG28 = "[public-session-exit-route]";
|
|
14084
14246
|
function isLoopbackAddr4(addr) {
|
|
14085
14247
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
14086
14248
|
}
|
|
@@ -14088,7 +14250,7 @@ var app35 = new Hono();
|
|
|
14088
14250
|
app35.post("/", async (c) => {
|
|
14089
14251
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
14090
14252
|
if (!isLoopbackAddr4(remoteAddr)) {
|
|
14091
|
-
console.error(`${
|
|
14253
|
+
console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
14092
14254
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
14093
14255
|
}
|
|
14094
14256
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -14097,7 +14259,7 @@ app35.post("/", async (c) => {
|
|
|
14097
14259
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
14098
14260
|
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
14099
14261
|
if (offender !== void 0) {
|
|
14100
|
-
console.error(`${
|
|
14262
|
+
console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
14101
14263
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
14102
14264
|
}
|
|
14103
14265
|
}
|
|
@@ -14115,7 +14277,7 @@ app35.post("/", async (c) => {
|
|
|
14115
14277
|
var public_session_exit_default = app35;
|
|
14116
14278
|
|
|
14117
14279
|
// server/routes/admin/access-session-evict.ts
|
|
14118
|
-
var
|
|
14280
|
+
var TAG29 = "[access-session-evict]";
|
|
14119
14281
|
function isLoopbackAddr5(addr) {
|
|
14120
14282
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
14121
14283
|
}
|
|
@@ -14123,7 +14285,7 @@ var app36 = new Hono();
|
|
|
14123
14285
|
app36.post("/", async (c) => {
|
|
14124
14286
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
14125
14287
|
if (!isLoopbackAddr5(remoteAddr)) {
|
|
14126
|
-
console.error(`${
|
|
14288
|
+
console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
14127
14289
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
14128
14290
|
}
|
|
14129
14291
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -14132,7 +14294,7 @@ app36.post("/", async (c) => {
|
|
|
14132
14294
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
14133
14295
|
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
14134
14296
|
if (offender !== void 0) {
|
|
14135
|
-
console.error(`${
|
|
14297
|
+
console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
14136
14298
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
14137
14299
|
}
|
|
14138
14300
|
}
|
|
@@ -14145,7 +14307,7 @@ app36.post("/", async (c) => {
|
|
|
14145
14307
|
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
14146
14308
|
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
14147
14309
|
const dropped = evictAccessSessionsByGrant(grantId);
|
|
14148
|
-
console.log(`${
|
|
14310
|
+
console.log(`${TAG29} grantId=${grantId} dropped=${dropped}`);
|
|
14149
14311
|
return c.json({ ok: true, dropped });
|
|
14150
14312
|
});
|
|
14151
14313
|
var access_session_evict_default = app36;
|
|
@@ -14425,7 +14587,7 @@ async function generateNewMagicToken(grantId) {
|
|
|
14425
14587
|
}
|
|
14426
14588
|
|
|
14427
14589
|
// server/routes/access/verify-token.ts
|
|
14428
|
-
var
|
|
14590
|
+
var TAG30 = "[access-verify]";
|
|
14429
14591
|
var MINT_TAG = "[access-session-mint]";
|
|
14430
14592
|
var COOKIE_NAME = "__access_session";
|
|
14431
14593
|
var app39 = new Hono();
|
|
@@ -14444,39 +14606,39 @@ app39.post("/", async (c) => {
|
|
|
14444
14606
|
}
|
|
14445
14607
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
14446
14608
|
if (rateMsg) {
|
|
14447
|
-
console.error(`${
|
|
14609
|
+
console.error(`${TAG30} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
14448
14610
|
return c.json({ error: rateMsg }, 429);
|
|
14449
14611
|
}
|
|
14450
14612
|
const grant = await findGrantByMagicToken(token);
|
|
14451
14613
|
if (!grant) {
|
|
14452
14614
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14453
|
-
console.error(`${
|
|
14615
|
+
console.error(`${TAG30} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
14454
14616
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
14455
14617
|
}
|
|
14456
14618
|
if (grant.agentSlug !== agentSlug) {
|
|
14457
14619
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14458
14620
|
console.error(
|
|
14459
|
-
`${
|
|
14621
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
14460
14622
|
);
|
|
14461
14623
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
14462
14624
|
}
|
|
14463
14625
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
14464
14626
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14465
14627
|
console.error(
|
|
14466
|
-
`${
|
|
14628
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
14467
14629
|
);
|
|
14468
14630
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
14469
14631
|
}
|
|
14470
14632
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
14471
14633
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14472
14634
|
console.error(
|
|
14473
|
-
`${
|
|
14635
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
14474
14636
|
);
|
|
14475
14637
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
14476
14638
|
}
|
|
14477
14639
|
if (!grant.sliceToken) {
|
|
14478
14640
|
console.error(
|
|
14479
|
-
`${
|
|
14641
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
14480
14642
|
);
|
|
14481
14643
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
14482
14644
|
}
|
|
@@ -14492,12 +14654,12 @@ app39.post("/", async (c) => {
|
|
|
14492
14654
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
14493
14655
|
} catch (err) {
|
|
14494
14656
|
console.error(
|
|
14495
|
-
`${
|
|
14657
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
14496
14658
|
);
|
|
14497
14659
|
return c.json({ error: "verification-failed" }, 500);
|
|
14498
14660
|
}
|
|
14499
14661
|
clearAccessRateLimit(ip, agentSlug);
|
|
14500
|
-
console.log(`${
|
|
14662
|
+
console.log(`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
14501
14663
|
console.log(
|
|
14502
14664
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
14503
14665
|
);
|
|
@@ -14573,7 +14735,7 @@ async function sendMagicLinkEmail(payload) {
|
|
|
14573
14735
|
}
|
|
14574
14736
|
|
|
14575
14737
|
// server/routes/access/request-magic-link.ts
|
|
14576
|
-
var
|
|
14738
|
+
var TAG31 = "[access-request-link]";
|
|
14577
14739
|
var app40 = new Hono();
|
|
14578
14740
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
14579
14741
|
app40.post("/", async (c) => {
|
|
@@ -14590,18 +14752,18 @@ app40.post("/", async (c) => {
|
|
|
14590
14752
|
}
|
|
14591
14753
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
14592
14754
|
if (rateMsg) {
|
|
14593
|
-
console.error(`${
|
|
14755
|
+
console.error(`${TAG31} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
14594
14756
|
return c.json({ error: rateMsg }, 429);
|
|
14595
14757
|
}
|
|
14596
14758
|
recordRequestLinkAttempt(contactValue);
|
|
14597
14759
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
14598
14760
|
if (!accountId) {
|
|
14599
|
-
console.error(`${
|
|
14761
|
+
console.error(`${TAG31} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
14600
14762
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14601
14763
|
}
|
|
14602
14764
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
14603
14765
|
if (!grant) {
|
|
14604
|
-
console.log(`${
|
|
14766
|
+
console.log(`${TAG31} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
14605
14767
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14606
14768
|
}
|
|
14607
14769
|
let token;
|
|
@@ -14609,7 +14771,7 @@ app40.post("/", async (c) => {
|
|
|
14609
14771
|
token = await generateNewMagicToken(grant.grantId);
|
|
14610
14772
|
} catch (err) {
|
|
14611
14773
|
console.error(
|
|
14612
|
-
`${
|
|
14774
|
+
`${TAG31} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
14613
14775
|
);
|
|
14614
14776
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14615
14777
|
}
|
|
@@ -14641,12 +14803,12 @@ app40.post("/", async (c) => {
|
|
|
14641
14803
|
});
|
|
14642
14804
|
if (!sendResult.ok) {
|
|
14643
14805
|
console.error(
|
|
14644
|
-
`${
|
|
14806
|
+
`${TAG31} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
14645
14807
|
);
|
|
14646
14808
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14647
14809
|
}
|
|
14648
14810
|
console.log(
|
|
14649
|
-
`${
|
|
14811
|
+
`${TAG31} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
14650
14812
|
);
|
|
14651
14813
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14652
14814
|
});
|
|
@@ -14800,7 +14962,7 @@ var sites_default = app42;
|
|
|
14800
14962
|
// app/lib/visitor-token.ts
|
|
14801
14963
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
14802
14964
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync21, writeFileSync as writeFileSync7 } from "fs";
|
|
14803
|
-
import { dirname as
|
|
14965
|
+
import { dirname as dirname6 } from "path";
|
|
14804
14966
|
var TOKEN_PREFIX = "v1.";
|
|
14805
14967
|
var SECRET_BYTES = 32;
|
|
14806
14968
|
var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -14817,7 +14979,7 @@ function getSecret() {
|
|
|
14817
14979
|
}
|
|
14818
14980
|
const fresh = randomBytes(SECRET_BYTES).toString("hex");
|
|
14819
14981
|
try {
|
|
14820
|
-
mkdirSync4(
|
|
14982
|
+
mkdirSync4(dirname6(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
|
|
14821
14983
|
writeFileSync7(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
|
|
14822
14984
|
console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
|
|
14823
14985
|
} catch {
|
|
@@ -14876,7 +15038,7 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
|
|
|
14876
15038
|
|
|
14877
15039
|
// app/lib/brand-config.ts
|
|
14878
15040
|
import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
|
|
14879
|
-
import { join as
|
|
15041
|
+
import { join as join19 } from "path";
|
|
14880
15042
|
var cached2 = null;
|
|
14881
15043
|
var cachedAttempted = false;
|
|
14882
15044
|
function readBrandConfig() {
|
|
@@ -14884,7 +15046,7 @@ function readBrandConfig() {
|
|
|
14884
15046
|
cachedAttempted = true;
|
|
14885
15047
|
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
|
|
14886
15048
|
if (!platformRoot2) return null;
|
|
14887
|
-
const brandPath =
|
|
15049
|
+
const brandPath = join19(platformRoot2, "config", "brand.json");
|
|
14888
15050
|
if (!existsSync22(brandPath)) return null;
|
|
14889
15051
|
try {
|
|
14890
15052
|
cached2 = JSON.parse(readFileSync22(brandPath, "utf-8"));
|
|
@@ -16330,6 +16492,262 @@ var WaGateway = class {
|
|
|
16330
16492
|
}
|
|
16331
16493
|
};
|
|
16332
16494
|
|
|
16495
|
+
// app/lib/webchat/gateway/inbound-hub.ts
|
|
16496
|
+
var InboundHub2 = class {
|
|
16497
|
+
keys = /* @__PURE__ */ new Map();
|
|
16498
|
+
state(key) {
|
|
16499
|
+
let s = this.keys.get(key);
|
|
16500
|
+
if (!s) {
|
|
16501
|
+
s = { subscriber: null, ready: false, inFlight: [] };
|
|
16502
|
+
this.keys.set(key, s);
|
|
16503
|
+
}
|
|
16504
|
+
return s;
|
|
16505
|
+
}
|
|
16506
|
+
/** Route an inbound message. Delivers immediately only if the key's channel
|
|
16507
|
+
* server is attached AND has signalled readiness; otherwise queues it (cold
|
|
16508
|
+
* start, or the attach->ready window) so it is never written before the
|
|
16509
|
+
* channel server can consume it. Every accepted message is tracked in-flight
|
|
16510
|
+
* until it reaches a terminal state. */
|
|
16511
|
+
deliver(payload, now) {
|
|
16512
|
+
const s = this.state(payload.key);
|
|
16513
|
+
if (s.subscriber && s.ready) {
|
|
16514
|
+
s.subscriber(payload);
|
|
16515
|
+
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now });
|
|
16516
|
+
} else {
|
|
16517
|
+
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null });
|
|
16518
|
+
}
|
|
16519
|
+
}
|
|
16520
|
+
/** A key's channel server connected. Register the subscriber but do NOT
|
|
16521
|
+
* drain yet — the drain waits for markReady so nothing is written before the
|
|
16522
|
+
* channel server is consuming. Replaces any prior subscriber. */
|
|
16523
|
+
attach(key, subscriber) {
|
|
16524
|
+
const s = this.state(key);
|
|
16525
|
+
s.subscriber = subscriber;
|
|
16526
|
+
s.ready = false;
|
|
16527
|
+
}
|
|
16528
|
+
/** The key's channel server reported it is connected and consuming. Drain
|
|
16529
|
+
* every queued message to it in arrival order, marking each drained-awaiting-
|
|
16530
|
+
* ack, then route live. Returns the number drained. */
|
|
16531
|
+
markReady(key, now) {
|
|
16532
|
+
const s = this.state(key);
|
|
16533
|
+
s.ready = true;
|
|
16534
|
+
if (!s.subscriber) return 0;
|
|
16535
|
+
let count = 0;
|
|
16536
|
+
for (const e of s.inFlight) {
|
|
16537
|
+
if (e.state === "queued") {
|
|
16538
|
+
s.subscriber(e.payload);
|
|
16539
|
+
e.state = "drained";
|
|
16540
|
+
e.drainedAt = now;
|
|
16541
|
+
count++;
|
|
16542
|
+
}
|
|
16543
|
+
}
|
|
16544
|
+
return count;
|
|
16545
|
+
}
|
|
16546
|
+
/** The channel server confirmed it received (emitted) a drained message. The
|
|
16547
|
+
* message has reached the agent; drop it from in-flight. Returns whether a
|
|
16548
|
+
* matching in-flight message was found. */
|
|
16549
|
+
ack(key, messageId) {
|
|
16550
|
+
const s = this.keys.get(key);
|
|
16551
|
+
if (!s) return false;
|
|
16552
|
+
const i = s.inFlight.findIndex((e) => e.payload.messageId === messageId);
|
|
16553
|
+
if (i < 0) return false;
|
|
16554
|
+
s.inFlight.splice(i, 1);
|
|
16555
|
+
return true;
|
|
16556
|
+
}
|
|
16557
|
+
/** A key's channel server disconnected (session ended/restarting).
|
|
16558
|
+
* Subsequent messages queue again until re-attach + ready, so nothing is lost
|
|
16559
|
+
* across a restart. In-flight drained messages stay tracked for ack/sweep. */
|
|
16560
|
+
detach(key) {
|
|
16561
|
+
const s = this.keys.get(key);
|
|
16562
|
+
if (s) {
|
|
16563
|
+
s.subscriber = null;
|
|
16564
|
+
s.ready = false;
|
|
16565
|
+
}
|
|
16566
|
+
}
|
|
16567
|
+
/** Reconciliation from queue state alone — does not wait for a future inbound
|
|
16568
|
+
* to expose a loss. Returns (and removes) a terminal event for every message
|
|
16569
|
+
* that never reached the agent:
|
|
16570
|
+
* - a still-queued message older than queuedThresholdMs WHILE a subscriber
|
|
16571
|
+
* is attached -> inbound-undelivered (the readiness handoff broke);
|
|
16572
|
+
* - a still-queued message older than queuedThresholdMs with NO subscriber
|
|
16573
|
+
* attached -> spawn-undelivered (the channel server never attached: spawn
|
|
16574
|
+
* failed, or the session ended and never resumed);
|
|
16575
|
+
* - a drained message unacked past ackThresholdMs -> drain-undelivered (the
|
|
16576
|
+
* channel server never confirmed receipt — the silent cold-start drop).
|
|
16577
|
+
* Each message is emitted at most once (removed when emitted). */
|
|
16578
|
+
sweep(now, t) {
|
|
16579
|
+
const events = [];
|
|
16580
|
+
for (const [key, s] of this.keys) {
|
|
16581
|
+
s.inFlight = s.inFlight.filter((e) => {
|
|
16582
|
+
if (e.state === "queued" && s.subscriber && now - e.enqueuedAt > t.queuedThresholdMs) {
|
|
16583
|
+
events.push({ kind: "inbound-undelivered", key, messageId: e.payload.messageId, ageMs: now - e.enqueuedAt });
|
|
16584
|
+
return false;
|
|
16585
|
+
}
|
|
16586
|
+
if (e.state === "queued" && !s.subscriber && now - e.enqueuedAt > t.queuedThresholdMs) {
|
|
16587
|
+
events.push({ kind: "spawn-undelivered", key, messageId: e.payload.messageId, ageMs: now - e.enqueuedAt });
|
|
16588
|
+
return false;
|
|
16589
|
+
}
|
|
16590
|
+
if (e.state === "drained" && e.drainedAt != null && now - e.drainedAt > t.ackThresholdMs) {
|
|
16591
|
+
events.push({ kind: "drain-undelivered", key, messageId: e.payload.messageId, ageMs: now - e.drainedAt });
|
|
16592
|
+
return false;
|
|
16593
|
+
}
|
|
16594
|
+
return true;
|
|
16595
|
+
});
|
|
16596
|
+
}
|
|
16597
|
+
return events;
|
|
16598
|
+
}
|
|
16599
|
+
/** Whether a key currently has a live channel server attached. The gateway
|
|
16600
|
+
* uses this to decide whether an inbound needs a cold-start spawn/resume. */
|
|
16601
|
+
hasSubscriber(key) {
|
|
16602
|
+
return this.keys.get(key)?.subscriber != null;
|
|
16603
|
+
}
|
|
16604
|
+
/** Whether a key's channel server is attached AND has signalled readiness.
|
|
16605
|
+
* Used only to log delivery=immediate vs queued accurately. */
|
|
16606
|
+
isReady(key) {
|
|
16607
|
+
const s = this.keys.get(key);
|
|
16608
|
+
return s?.subscriber != null && s.ready;
|
|
16609
|
+
}
|
|
16610
|
+
};
|
|
16611
|
+
|
|
16612
|
+
// app/lib/webchat/gateway/routes.ts
|
|
16613
|
+
function createWebchatChannelRoutes(deps) {
|
|
16614
|
+
const app48 = new Hono();
|
|
16615
|
+
app48.get("/webchat-channel/inbound", (c) => {
|
|
16616
|
+
const key = c.req.query("key");
|
|
16617
|
+
if (!key) return c.json({ error: "key required" }, 400);
|
|
16618
|
+
return streamSSE(c, async (stream2) => {
|
|
16619
|
+
deps.hub.attach(key, (payload) => {
|
|
16620
|
+
void stream2.writeSSE({ data: JSON.stringify(payload) });
|
|
16621
|
+
});
|
|
16622
|
+
console.error(`[webchat:inbound] op=channel-attached key=${key}`);
|
|
16623
|
+
stream2.onAbort(() => {
|
|
16624
|
+
deps.hub.detach(key);
|
|
16625
|
+
console.error(`[webchat:inbound] op=channel-detached key=${key}`);
|
|
16626
|
+
});
|
|
16627
|
+
while (!stream2.aborted) {
|
|
16628
|
+
await stream2.sleep(15e3);
|
|
16629
|
+
await stream2.writeSSE({ data: "", event: "ping" });
|
|
16630
|
+
}
|
|
16631
|
+
});
|
|
16632
|
+
});
|
|
16633
|
+
app48.post("/webchat-channel/reply", async (c) => {
|
|
16634
|
+
const body = await c.req.json().catch(() => null);
|
|
16635
|
+
const key = body?.key;
|
|
16636
|
+
const text = body?.text;
|
|
16637
|
+
if (typeof key !== "string" || typeof text !== "string") {
|
|
16638
|
+
return c.json({ error: "key and text required" }, 400);
|
|
16639
|
+
}
|
|
16640
|
+
deps.onReply?.(key, Buffer.byteLength(text, "utf8"));
|
|
16641
|
+
return c.json({ ok: true });
|
|
16642
|
+
});
|
|
16643
|
+
app48.post("/webchat-channel/ready", async (c) => {
|
|
16644
|
+
const body = await c.req.json().catch(() => null);
|
|
16645
|
+
const key = body?.key;
|
|
16646
|
+
if (typeof key !== "string") {
|
|
16647
|
+
return c.json({ error: "key required" }, 400);
|
|
16648
|
+
}
|
|
16649
|
+
deps.onReady?.(key);
|
|
16650
|
+
return c.json({ ok: true });
|
|
16651
|
+
});
|
|
16652
|
+
app48.post("/webchat-channel/received", async (c) => {
|
|
16653
|
+
const body = await c.req.json().catch(() => null);
|
|
16654
|
+
const key = body?.key;
|
|
16655
|
+
const messageId = body?.messageId;
|
|
16656
|
+
if (typeof key !== "string" || typeof messageId !== "string") {
|
|
16657
|
+
return c.json({ error: "key and messageId required" }, 400);
|
|
16658
|
+
}
|
|
16659
|
+
deps.onReceived?.(key, messageId);
|
|
16660
|
+
return c.json({ ok: true });
|
|
16661
|
+
});
|
|
16662
|
+
return app48;
|
|
16663
|
+
}
|
|
16664
|
+
|
|
16665
|
+
// app/lib/webchat/gateway/webchat-gateway.ts
|
|
16666
|
+
var QUEUED_THRESHOLD_MS2 = 3e4;
|
|
16667
|
+
var ACK_THRESHOLD_MS2 = 3e4;
|
|
16668
|
+
var SWEEP_INTERVAL_MS2 = 15e3;
|
|
16669
|
+
var WebchatGateway = class {
|
|
16670
|
+
constructor(deps) {
|
|
16671
|
+
this.deps = deps;
|
|
16672
|
+
}
|
|
16673
|
+
hub = new InboundHub2();
|
|
16674
|
+
spawning = /* @__PURE__ */ new Set();
|
|
16675
|
+
/** Last delivery time per key, the latency basis for the op=reply line. */
|
|
16676
|
+
lastDeliveredAt = /* @__PURE__ */ new Map();
|
|
16677
|
+
seq = 0;
|
|
16678
|
+
/** Hono sub-app exposing the loopback channel routes; mount on the UI app. */
|
|
16679
|
+
routes() {
|
|
16680
|
+
return createWebchatChannelRoutes({
|
|
16681
|
+
hub: this.hub,
|
|
16682
|
+
onReady: (key) => {
|
|
16683
|
+
const now = Date.now();
|
|
16684
|
+
const count = this.hub.markReady(key, now);
|
|
16685
|
+
if (count > 0) {
|
|
16686
|
+
console.error(`[webchat:inbound] op=drained key=${key} count=${count}`);
|
|
16687
|
+
this.lastDeliveredAt.set(key, now);
|
|
16688
|
+
}
|
|
16689
|
+
},
|
|
16690
|
+
onReceived: (key, messageId) => {
|
|
16691
|
+
const found = this.hub.ack(key, messageId);
|
|
16692
|
+
console.error(
|
|
16693
|
+
`[webchat:inbound] op=channel-received key=${key} messageId=${messageId}${found ? "" : " note=unknown"}`
|
|
16694
|
+
);
|
|
16695
|
+
},
|
|
16696
|
+
onReply: (key, bytes) => {
|
|
16697
|
+
const deliveredAt = this.lastDeliveredAt.get(key);
|
|
16698
|
+
const latency = deliveredAt != null ? Date.now() - deliveredAt : -1;
|
|
16699
|
+
console.error(`[webchat:inbound] op=reply key=${key} bytes=${bytes} latencyMs=${latency}`);
|
|
16700
|
+
}
|
|
16701
|
+
});
|
|
16702
|
+
}
|
|
16703
|
+
/** Run one reconciliation pass: log every terminal delivery event the sweep
|
|
16704
|
+
* surfaces. Returns the events (for tests). Driven by startSweeper() in
|
|
16705
|
+
* production; called directly with an injected clock in tests. */
|
|
16706
|
+
reconcile(now) {
|
|
16707
|
+
const events = this.hub.sweep(now, {
|
|
16708
|
+
queuedThresholdMs: QUEUED_THRESHOLD_MS2,
|
|
16709
|
+
ackThresholdMs: ACK_THRESHOLD_MS2
|
|
16710
|
+
});
|
|
16711
|
+
for (const e of events) {
|
|
16712
|
+
console.error(`[webchat:inbound] op=${e.kind} key=${e.key} messageId=${e.messageId} ageMs=${e.ageMs}`);
|
|
16713
|
+
}
|
|
16714
|
+
return events;
|
|
16715
|
+
}
|
|
16716
|
+
/** Start the periodic reconciliation sweep in the long-lived UI process. */
|
|
16717
|
+
startSweeper(intervalMs = SWEEP_INTERVAL_MS2) {
|
|
16718
|
+
return setInterval(() => this.reconcile(Date.now()), intervalMs);
|
|
16719
|
+
}
|
|
16720
|
+
/** Handle one inbound webchat message (browser POST → here). */
|
|
16721
|
+
async handleInbound(input) {
|
|
16722
|
+
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
16723
|
+
const hadSubscriber = this.hub.hasSubscriber(input.key);
|
|
16724
|
+
const willDeliverNow = this.hub.isReady(input.key);
|
|
16725
|
+
this.hub.deliver(
|
|
16726
|
+
{ key: input.key, text: input.text, messageId: `wc-${++this.seq}` },
|
|
16727
|
+
Date.now()
|
|
16728
|
+
);
|
|
16729
|
+
if (willDeliverNow) this.lastDeliveredAt.set(input.key, Date.now());
|
|
16730
|
+
console.error(
|
|
16731
|
+
`[webchat:inbound] op=received key=${input.key.slice(0, 8)} role=${input.role} bytes=${bytes} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
16732
|
+
);
|
|
16733
|
+
if (!hadSubscriber && !this.spawning.has(input.key)) {
|
|
16734
|
+
this.spawning.add(input.key);
|
|
16735
|
+
console.error(`[webchat:inbound] op=spawn-trigger key=${input.key} role=${input.role}`);
|
|
16736
|
+
try {
|
|
16737
|
+
await this.deps.ensureChannelSession({
|
|
16738
|
+
accountId: input.accountId,
|
|
16739
|
+
key: input.key,
|
|
16740
|
+
role: input.role,
|
|
16741
|
+
gatewayUrl: this.deps.gatewayUrl,
|
|
16742
|
+
serverPath: this.deps.serverPath
|
|
16743
|
+
});
|
|
16744
|
+
} finally {
|
|
16745
|
+
this.spawning.delete(input.key);
|
|
16746
|
+
}
|
|
16747
|
+
}
|
|
16748
|
+
}
|
|
16749
|
+
};
|
|
16750
|
+
|
|
16333
16751
|
// app/lib/whatsapp/gateway/native-file-follower.ts
|
|
16334
16752
|
var COMPOSING_REFRESH_MS = 1e4;
|
|
16335
16753
|
function startNativeFileFollower(input) {
|
|
@@ -16686,7 +17104,7 @@ function clientFrom(c) {
|
|
|
16686
17104
|
);
|
|
16687
17105
|
}
|
|
16688
17106
|
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
16689
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT9 ?
|
|
17107
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join20(PLATFORM_ROOT9, "config", "brand.json") : "";
|
|
16690
17108
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
16691
17109
|
if (BRAND_JSON_PATH && !existsSync25(BRAND_JSON_PATH)) {
|
|
16692
17110
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
@@ -16712,7 +17130,7 @@ var brandLoginOpts = {
|
|
|
16712
17130
|
bodyFont: BRAND.defaultFonts?.body,
|
|
16713
17131
|
logoContainsName: !!BRAND.logoContainsName
|
|
16714
17132
|
};
|
|
16715
|
-
var ALIAS_DOMAINS_PATH =
|
|
17133
|
+
var ALIAS_DOMAINS_PATH = join20(homedir3(), BRAND.configDir, "alias-domains.json");
|
|
16716
17134
|
function loadAliasDomains() {
|
|
16717
17135
|
try {
|
|
16718
17136
|
if (!existsSync25(ALIAS_DOMAINS_PATH)) return null;
|
|
@@ -16744,7 +17162,7 @@ var app47 = new Hono();
|
|
|
16744
17162
|
var nativeFileFollowers = /* @__PURE__ */ new Map();
|
|
16745
17163
|
var waGateway = new WaGateway({
|
|
16746
17164
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
16747
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve27(process.env.MAXY_PLATFORM_ROOT ??
|
|
17165
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve27(process.env.MAXY_PLATFORM_ROOT ?? join20(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
16748
17166
|
// Task 751 — file delivery on the native channel: resolve the platform
|
|
16749
17167
|
// account + path validation here and funnel through the shared send core.
|
|
16750
17168
|
sendDocument: async ({ senderId, accountId, filePath, caption }) => {
|
|
@@ -16760,7 +17178,7 @@ var waGateway = new WaGateway({
|
|
|
16760
17178
|
caption,
|
|
16761
17179
|
accountId,
|
|
16762
17180
|
maxyAccountId,
|
|
16763
|
-
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ??
|
|
17181
|
+
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join20(__dirname, ".."))
|
|
16764
17182
|
});
|
|
16765
17183
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
16766
17184
|
},
|
|
@@ -16798,6 +17216,29 @@ var waGateway = new WaGateway({
|
|
|
16798
17216
|
});
|
|
16799
17217
|
app47.route("/", waGateway.routes());
|
|
16800
17218
|
waGateway.startSweeper();
|
|
17219
|
+
var webchatGateway = new WebchatGateway({
|
|
17220
|
+
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
17221
|
+
serverPath: process.env.MAXY_WEBCHAT_CHANNEL_SERVER_PATH ?? resolve27(process.env.MAXY_PLATFORM_ROOT ?? join20(__dirname, ".."), "services/webchat-channel/dist/server.js"),
|
|
17222
|
+
ensureChannelSession: async ({ accountId, key, role, gatewayUrl, serverPath }) => {
|
|
17223
|
+
if (role !== "admin") {
|
|
17224
|
+
console.error(`[webchat:inbound] op=spawn-refused key=${key} role=${role} reason=public-path-not-wired`);
|
|
17225
|
+
return;
|
|
17226
|
+
}
|
|
17227
|
+
const result = await managerRcSpawn({
|
|
17228
|
+
sessionId: adminSessionIdFor(accountId, key),
|
|
17229
|
+
role: "admin",
|
|
17230
|
+
channel: "webchat",
|
|
17231
|
+
senderId: key,
|
|
17232
|
+
webchatChannel: { key, gatewayUrl, serverPath },
|
|
17233
|
+
logContext: `channel=webchat key=${key} role=${role}`
|
|
17234
|
+
});
|
|
17235
|
+
if ("error" in result) {
|
|
17236
|
+
console.error(`[webchat:inbound] op=spawn-failed key=${key} error=${result.error} status=${result.status}`);
|
|
17237
|
+
}
|
|
17238
|
+
}
|
|
17239
|
+
});
|
|
17240
|
+
app47.route("/", webchatGateway.routes());
|
|
17241
|
+
webchatGateway.startSweeper();
|
|
16801
17242
|
app47.use("*", clientIpMiddleware);
|
|
16802
17243
|
app47.use("*", async (c, next) => {
|
|
16803
17244
|
await next();
|
|
@@ -17084,6 +17525,7 @@ app47.route("/api/health", health_default);
|
|
|
17084
17525
|
app47.route("/api/chat", chat_default);
|
|
17085
17526
|
app47.route("/api/whatsapp", whatsapp_default);
|
|
17086
17527
|
app47.route("/api/whatsapp-reader", whatsapp_reader_default);
|
|
17528
|
+
app47.route("/api/webchat", createWebchatRoutes({ handleInbound: (input) => webchatGateway.handleInbound(input) }));
|
|
17087
17529
|
app47.route("/api/telegram", telegram_default);
|
|
17088
17530
|
app47.route("/api/onboarding", onboarding_default);
|
|
17089
17531
|
app47.route("/api/admin", admin_default);
|
|
@@ -17191,7 +17633,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
17191
17633
|
function readInstalledVersion() {
|
|
17192
17634
|
try {
|
|
17193
17635
|
if (!PLATFORM_ROOT9) return "unknown";
|
|
17194
|
-
const versionFile =
|
|
17636
|
+
const versionFile = join20(PLATFORM_ROOT9, "config", `.${BRAND.hostname}-version`);
|
|
17195
17637
|
if (!existsSync25(versionFile)) return "unknown";
|
|
17196
17638
|
const content = readFileSync24(versionFile, "utf-8").trim();
|
|
17197
17639
|
return content || "unknown";
|
|
@@ -17250,11 +17692,11 @@ ${clientErrorReporterScript}
|
|
|
17250
17692
|
}
|
|
17251
17693
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
17252
17694
|
function loadBrandingCache(agentSlug) {
|
|
17253
|
-
const configDir2 =
|
|
17695
|
+
const configDir2 = join20(homedir3(), BRAND.configDir);
|
|
17254
17696
|
try {
|
|
17255
17697
|
const accountId = getDefaultAccountId();
|
|
17256
17698
|
if (!accountId) return null;
|
|
17257
|
-
const cachePath =
|
|
17699
|
+
const cachePath = join20(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
17258
17700
|
if (!existsSync25(cachePath)) return null;
|
|
17259
17701
|
return JSON.parse(readFileSync24(cachePath, "utf-8"));
|
|
17260
17702
|
} catch {
|
|
@@ -17320,7 +17762,7 @@ app47.get("/public", (c) => {
|
|
|
17320
17762
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
17321
17763
|
return c.html(cachedHtml("public.html"));
|
|
17322
17764
|
});
|
|
17323
|
-
app47.get("/chat", (c) => {
|
|
17765
|
+
app47.get("/public-chat", (c) => {
|
|
17324
17766
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
17325
17767
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
17326
17768
|
return c.html(cachedHtml("public.html"));
|
|
@@ -17383,6 +17825,11 @@ app47.get("/graph", (c) => {
|
|
|
17383
17825
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
17384
17826
|
return c.html(cachedHtml("graph.html"));
|
|
17385
17827
|
});
|
|
17828
|
+
app47.get("/chat", (c) => {
|
|
17829
|
+
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
17830
|
+
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
17831
|
+
return c.html(cachedHtml("chat.html"));
|
|
17832
|
+
});
|
|
17386
17833
|
app47.get("/sessions", (c) => {
|
|
17387
17834
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
17388
17835
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
@@ -17552,7 +17999,7 @@ try {
|
|
|
17552
17999
|
} catch (err) {
|
|
17553
18000
|
console.error(`[graph-health] account-enumeration unavailable reason=${err instanceof Error ? err.message : String(err)}`);
|
|
17554
18001
|
}
|
|
17555
|
-
var configDirForWhatsApp =
|
|
18002
|
+
var configDirForWhatsApp = basename8(MAXY_DIR) || ".maxy";
|
|
17556
18003
|
var bootAccount = resolveAccount();
|
|
17557
18004
|
if (bootAccount) {
|
|
17558
18005
|
migrateRemovedConfigKeys(bootAccount.accountDir);
|
|
@@ -17614,7 +18061,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
17614
18061
|
}
|
|
17615
18062
|
init({
|
|
17616
18063
|
configDir: configDirForWhatsApp,
|
|
17617
|
-
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ??
|
|
18064
|
+
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join20(__dirname, "..")),
|
|
17618
18065
|
accountConfig: bootAccountConfig,
|
|
17619
18066
|
onMessage: async (msg) => {
|
|
17620
18067
|
if (msg.isOwnerMirror) {
|