@rubytech/create-maxy-code 0.1.289 → 0.1.291
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 +3 -1
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +21 -0
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +9 -5
- 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/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 +27 -13
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +32 -15
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/server/public/assets/AdminShell-BgPlhiCM.js +1 -0
- package/payload/server/public/assets/{Checkbox-CsQq7YPC.js → Checkbox-DWx2NcU9.js} +1 -1
- package/payload/server/public/assets/SidebarSplitter-CidskiAr.js +1 -0
- package/payload/server/public/assets/Transcript-iuM5b4JT.js +1 -0
- package/payload/server/public/assets/admin-Cdz561nK.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-PGePlVpb.js → architectureDiagram-Q4EWVU46-AtvCAoKj.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BJCbJRRs.js → blockDiagram-DXYQGD6D-CwUSTR3t.js} +1 -1
- package/payload/server/public/assets/browser-Bwd_rYu9.js +1 -0
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-DcMhi4gF.js → c4Diagram-AHTNJAMY-Btkecaf-.js} +1 -1
- package/payload/server/public/assets/channel-1MEkxMOf.js +1 -0
- package/payload/server/public/assets/chat-wbKqew6m.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-0vhS0MI-.js → chunk-336JU56O-fJXOXFRr.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-28Pgly06.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-dgx3qIz_.js → chunk-4TB4RGXK-IFvQOpiF.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-BglTosdT.js → chunk-5FUZZQ4R-DD-XOAcm.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-hZB3AxUF.js → chunk-5PVQY5BW-Ck-5mL2A.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-DXqLJyYa.js → chunk-EDXVE4YY-BPqvO0TO.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-BD4I_a54.js → chunk-ENJZ2VHE-PN6Hbw2D.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-BdQx0Ahc.js → chunk-ICPOFSXX-DkmwqyfQ.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-BDjtbZvS.js → chunk-OYMX7WX6-CWyihoif.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-COXR_N4N.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-hYN7A3g_.js → chunk-U2HBQHQK-BCWQB0k9.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-D3njJ7WG.js → chunk-X2U36JSP-CcaCMsL0.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-BfZKEcvU.js → chunk-YZCP3GAM-BUTzrAoG.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-DLJMNqLn.js → chunk-ZZ45TVLE-BD9UGg96.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BcD2DnVi.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-ChvbGJh0.js +1 -0
- package/payload/server/public/assets/clone-DPfDeykW.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-KV5264BT-DGjcX5uz.js → dagre-KV5264BT-DcOMoMzF.js} +1 -1
- package/payload/server/public/assets/{dagre-DVLE4-m6.js → dagre-dnTAouT6.js} +1 -1
- package/payload/server/public/assets/data-BSt0roMQ.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-CQI2o8Cv.js → diagram-5BDNPKRD-BuQUC75n.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-C8kXsEdQ.js → diagram-G4DWMVQ6-DHiu9U7B.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-DaWrNg_c.js → diagram-MMDJMWI5-ypu7YJGF.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-Ql9QPUxX.js → diagram-TYMM5635-DIwuWuqN.js} +1 -1
- package/payload/server/public/assets/{dist-BmvZ8OaX.js → dist-ePW3hz19.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-B2fBZhPo.js → erDiagram-SMLLAGMA-CKxGMQZW.js} +1 -1
- package/payload/server/public/assets/{flatten-Bo6YRmWl.js → flatten-CpKIi5d2.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BA0LiOm5.js → flowDiagram-DWJPFMVM-CqH2RE7a.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CBKdEgUk.js → ganttDiagram-T4ZO3ILL-BfuGXUUh.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CwwaM7ed.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BIyEfuwB.js → gitGraphDiagram-UUTBAWPF-BlpUYLLd.js} +1 -1
- package/payload/server/public/assets/{graph-BxWBNsFs.js → graph-BEu_XQMZ.js} +19 -19
- package/payload/server/public/assets/graph-labels-DvO7i7Ez.js +1 -0
- package/payload/server/public/assets/{graphlib-Cv4SvsQN.js → graphlib-O_QsiopN.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-CiiU112m.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-tHUu3AX-.js +2 -0
- package/payload/server/public/assets/{isEmpty-D6Kr-M1M.js → isEmpty-C3Vxk1It.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-haFza9s4.js → ishikawaDiagram-UXIWVN3A-CVMbR1ZD.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-l-8tJ21z.js → journeyDiagram-VCZTEJTY-7pbwsn7f.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DSZUvFLs.js → kanban-definition-6JOO6SKY-CWFMjHiJ.js} +1 -1
- package/payload/server/public/assets/{line-mfTElknw.js → line-B4V-vQdN.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-4temHHPS.js → mermaid-parser.core-Ba5Re-_1.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-DO2iS9my.js → mermaid.core-C39ZL-9e.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-Cymf2IGG.js → mindmap-definition-QFDTVHPH-BrREiATP.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-_2tmuvMb.js → pieDiagram-DEJITSTG-sJOzKUIf.js} +1 -1
- package/payload/server/public/assets/{public-Rz_GzOrN.js → public-927mDSLH.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CVCrGnBi.js → quadrantDiagram-34T5L4WZ-bxoGUruT.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-Bc-o8i8Z.js → requirementDiagram-MS252O5E-C8_uEDnp.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DrTBxqE6.js → sankeyDiagram-XADWPNL6-CKpSBj6p.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BjVjSsUJ.js → sequenceDiagram-FGHM5R23-BbVOmtji.js} +1 -1
- package/payload/server/public/assets/{src-DLEIsjER.js → src-DssjGmtU.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DWAJP6ly.js → stateDiagram-FHFEXIEX-CLiLypPX.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Cn6OBfIT.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-Br_nbteH.js → timeline-definition-GMOUNBTQ-Dzzc-pnB.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-hZ5bdL8b.js → useSelectionMode-BBvWzgtz.js} +1 -1
- package/payload/server/public/assets/useSelectionMode-CzWzToaM.css +1 -0
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-D_ozeKV6.js → vennDiagram-DHZGUBPP-CEj_ctTP.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-BYLmRSDV.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-B87ZKC2B.js → wardleyDiagram-NUSXRM2D-Cnbz79qd.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-DosRlk2T.js → xychartDiagram-5P7HB3ND-MTjLweae.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 +852 -349
- package/payload/server/public/assets/AdminShell-DkP2rJdF.js +0 -1
- package/payload/server/public/assets/admin-C_bkVvXh.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-xHbVjatf.js +0 -1
- package/payload/server/public/assets/browser-DynPFG_A.js +0 -1
- package/payload/server/public/assets/channel-idaGQqlP.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-DbVU-p-q.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-CHE_iZO7.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CmRFpirU.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DFy8xZjP.js +0 -1
- package/payload/server/public/assets/clone-kFPktUPQ.js +0 -1
- package/payload/server/public/assets/data-D1FFhz2k.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CmHdoS27.js +0 -1
- package/payload/server/public/assets/graph-labels-DSLdDXoF.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-DTA9msO-.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-DovHLDHz.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-BHDyrxpj.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-BLb-o9RX.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
|
@@ -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
|
}
|
|
@@ -2515,6 +2564,13 @@ async function sendComposing(sock, to) {
|
|
|
2515
2564
|
} catch {
|
|
2516
2565
|
}
|
|
2517
2566
|
}
|
|
2567
|
+
async function sendPaused(sock, to) {
|
|
2568
|
+
try {
|
|
2569
|
+
const jid = to.includes("@") ? to : toWhatsappJid(to);
|
|
2570
|
+
await sock.sendPresenceUpdate("paused", jid);
|
|
2571
|
+
} catch {
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2518
2574
|
async function sendReadReceipt(sock, chatJid, messageIds, participant) {
|
|
2519
2575
|
try {
|
|
2520
2576
|
await sock.readMessages([
|
|
@@ -2566,20 +2622,20 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
2566
2622
|
const messageId = result?.key?.id;
|
|
2567
2623
|
if (messageId) {
|
|
2568
2624
|
trackAgentSentMessage(messageId);
|
|
2569
|
-
console.error(`${
|
|
2625
|
+
console.error(`${TAG6} sent ${media.type} to=${jid} id=${messageId}`);
|
|
2570
2626
|
}
|
|
2571
2627
|
if (opts?.accountId) {
|
|
2572
2628
|
recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: media.type });
|
|
2573
2629
|
}
|
|
2574
2630
|
return { success: true, messageId: messageId ?? void 0 };
|
|
2575
2631
|
} catch (err) {
|
|
2576
|
-
console.error(`${
|
|
2632
|
+
console.error(`${TAG6} send media failed to=${to}: ${String(err)}`);
|
|
2577
2633
|
return { success: false, error: String(err) };
|
|
2578
2634
|
}
|
|
2579
2635
|
}
|
|
2580
2636
|
|
|
2581
2637
|
// app/lib/whatsapp/persist-message.ts
|
|
2582
|
-
var
|
|
2638
|
+
var TAG7 = "[whatsapp-persist]";
|
|
2583
2639
|
var sessionWriteLocks = /* @__PURE__ */ new Map();
|
|
2584
2640
|
async function persistWhatsAppMessage(input) {
|
|
2585
2641
|
if (!input.body || !input.msgKeyId) return null;
|
|
@@ -2684,7 +2740,7 @@ async function persistWhatsAppMessage(input) {
|
|
|
2684
2740
|
const result = await session.run(cypher, params);
|
|
2685
2741
|
const ms = Date.now() - t0;
|
|
2686
2742
|
if (result.records.length === 0) {
|
|
2687
|
-
console.error(`${
|
|
2743
|
+
console.error(`${TAG7} skip reason=conversation-not-found accountId=${input.platformAccountId} cacheKey=${input.cacheKey} messageId=${messageId}`);
|
|
2688
2744
|
return null;
|
|
2689
2745
|
}
|
|
2690
2746
|
const rec = result.records[0];
|
|
@@ -2693,26 +2749,26 @@ async function persistWhatsAppMessage(input) {
|
|
|
2693
2749
|
const senderElementId = rec.get("senderElementId");
|
|
2694
2750
|
const senderReused = rec.get("senderReused") === true;
|
|
2695
2751
|
if (existed) {
|
|
2696
|
-
console.error(`${
|
|
2752
|
+
console.error(`${TAG7} noop reason=existing-message messageId=${messageId}`);
|
|
2697
2753
|
return { messageId, created: false, senderElementId };
|
|
2698
2754
|
}
|
|
2699
2755
|
console.error(
|
|
2700
|
-
`${
|
|
2756
|
+
`${TAG7} sender-resolved telephone=${senderTelephone} nodeKind=Person elementId=${senderElementId} reused=${senderReused}`
|
|
2701
2757
|
);
|
|
2702
2758
|
if (prevMessageId) {
|
|
2703
|
-
console.error(`${
|
|
2759
|
+
console.error(`${TAG7} next-merge prior=${prevMessageId} next=${messageId}`);
|
|
2704
2760
|
} else {
|
|
2705
|
-
console.error(`${
|
|
2761
|
+
console.error(`${TAG7} next-skip reason=no-prior msgId=${messageId}`);
|
|
2706
2762
|
}
|
|
2707
2763
|
console.error(
|
|
2708
|
-
`${
|
|
2764
|
+
`${TAG7} write messageId=${messageId} accountId=${input.platformAccountId} cacheKey=${input.cacheKey} fromMe=${input.fromMe} dateSent=${dateSentIso} bodyBytes=${Buffer.byteLength(input.body, "utf8")} ms=${ms}`
|
|
2709
2765
|
);
|
|
2710
2766
|
return { messageId, created: true, senderElementId };
|
|
2711
2767
|
} catch (err) {
|
|
2712
2768
|
const ms = Date.now() - t0;
|
|
2713
2769
|
const reason = sanitizeReason(err);
|
|
2714
2770
|
console.error(
|
|
2715
|
-
`${
|
|
2771
|
+
`${TAG7} FAIL accountId=${input.platformAccountId} waname=${input.accountId} remoteJid=${input.remoteJid} msgKey=${input.msgKeyId} reason=${reason} ms=${ms}`
|
|
2716
2772
|
);
|
|
2717
2773
|
return null;
|
|
2718
2774
|
} finally {
|
|
@@ -2745,7 +2801,7 @@ function sanitizeReason(err) {
|
|
|
2745
2801
|
}
|
|
2746
2802
|
|
|
2747
2803
|
// app/lib/whatsapp/ensure-conversation.ts
|
|
2748
|
-
var
|
|
2804
|
+
var TAG8 = "[whatsapp-persist]";
|
|
2749
2805
|
async function ensureWhatsAppConversation(input) {
|
|
2750
2806
|
const t0 = Date.now();
|
|
2751
2807
|
try {
|
|
@@ -2757,19 +2813,19 @@ async function ensureWhatsAppConversation(input) {
|
|
|
2757
2813
|
const ms = Date.now() - t0;
|
|
2758
2814
|
if (!result.sessionId) {
|
|
2759
2815
|
console.error(
|
|
2760
|
-
`${
|
|
2816
|
+
`${TAG8} conversation-merged FAIL cacheKey=${input.cacheKey} reason=null-sessionId ms=${ms}`
|
|
2761
2817
|
);
|
|
2762
2818
|
return null;
|
|
2763
2819
|
}
|
|
2764
2820
|
console.error(
|
|
2765
|
-
`${
|
|
2821
|
+
`${TAG8} conversation-merged cacheKey=${input.cacheKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
|
|
2766
2822
|
);
|
|
2767
2823
|
return { sessionId: result.sessionId, created: result.created };
|
|
2768
2824
|
} catch (err) {
|
|
2769
2825
|
const ms = Date.now() - t0;
|
|
2770
2826
|
const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
|
|
2771
2827
|
console.error(
|
|
2772
|
-
`${
|
|
2828
|
+
`${TAG8} conversation-merged FAIL cacheKey=${input.cacheKey} reason=${reason} ms=${ms}`
|
|
2773
2829
|
);
|
|
2774
2830
|
return null;
|
|
2775
2831
|
}
|
|
@@ -2860,7 +2916,7 @@ import {
|
|
|
2860
2916
|
downloadContentFromMessage,
|
|
2861
2917
|
normalizeMessageContent as normalizeMessageContent2
|
|
2862
2918
|
} from "@whiskeysockets/baileys";
|
|
2863
|
-
var
|
|
2919
|
+
var TAG9 = "[whatsapp:media]";
|
|
2864
2920
|
var MEDIA_DIR = "/tmp/maxy-media";
|
|
2865
2921
|
function mimeToExt(mimetype) {
|
|
2866
2922
|
const map = {
|
|
@@ -2916,25 +2972,25 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
2916
2972
|
}
|
|
2917
2973
|
);
|
|
2918
2974
|
if (!buffer || buffer.length === 0) {
|
|
2919
|
-
console.error(`${
|
|
2975
|
+
console.error(`${TAG9} primary download returned empty, trying direct fallback`);
|
|
2920
2976
|
const downloadable = getDownloadableContent(content);
|
|
2921
2977
|
if (downloadable) {
|
|
2922
2978
|
try {
|
|
2923
2979
|
const stream2 = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
2924
2980
|
buffer = await streamToBuffer(stream2);
|
|
2925
2981
|
} catch (fallbackErr) {
|
|
2926
|
-
console.error(`${
|
|
2982
|
+
console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
|
|
2927
2983
|
}
|
|
2928
2984
|
}
|
|
2929
2985
|
}
|
|
2930
2986
|
if (!buffer || buffer.length === 0) {
|
|
2931
|
-
console.error(`${
|
|
2987
|
+
console.error(`${TAG9} download failed: empty buffer for ${mimetype ?? "unknown"}`);
|
|
2932
2988
|
return void 0;
|
|
2933
2989
|
}
|
|
2934
2990
|
if (buffer.length > maxBytes) {
|
|
2935
2991
|
const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
|
|
2936
2992
|
const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
|
|
2937
|
-
console.error(`${
|
|
2993
|
+
console.error(`${TAG9} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
|
|
2938
2994
|
return void 0;
|
|
2939
2995
|
}
|
|
2940
2996
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
@@ -2943,20 +2999,20 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
2943
2999
|
const filePath = join2(MEDIA_DIR, filename);
|
|
2944
3000
|
await writeFile(filePath, buffer);
|
|
2945
3001
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
2946
|
-
console.error(`${
|
|
3002
|
+
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
2947
3003
|
return {
|
|
2948
3004
|
path: filePath,
|
|
2949
3005
|
mimetype: mimetype ?? "application/octet-stream",
|
|
2950
3006
|
size: buffer.length
|
|
2951
3007
|
};
|
|
2952
3008
|
} catch (err) {
|
|
2953
|
-
console.error(`${
|
|
3009
|
+
console.error(`${TAG9} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
|
|
2954
3010
|
return void 0;
|
|
2955
3011
|
}
|
|
2956
3012
|
}
|
|
2957
3013
|
|
|
2958
3014
|
// app/lib/whatsapp/inbound/debounce.ts
|
|
2959
|
-
var
|
|
3015
|
+
var TAG10 = "[whatsapp:debounce]";
|
|
2960
3016
|
var STT_TAG = "[whatsapp:stt-await]";
|
|
2961
3017
|
function createInboundDebouncer(opts) {
|
|
2962
3018
|
const { debounceMs, buildKey, onFlush, onError } = opts;
|
|
@@ -2987,7 +3043,7 @@ function createInboundDebouncer(opts) {
|
|
|
2987
3043
|
pending.delete(key);
|
|
2988
3044
|
const batchSize = batch.entries.length;
|
|
2989
3045
|
try {
|
|
2990
|
-
console.error(`${
|
|
3046
|
+
console.error(`${TAG10} debounce flush key=${key} batchSize=${batchSize}`);
|
|
2991
3047
|
const result = onFlush(batch.entries);
|
|
2992
3048
|
if (result && typeof result.catch === "function") {
|
|
2993
3049
|
result.catch(onError);
|
|
@@ -3067,7 +3123,7 @@ function combineInboundBatch(entries) {
|
|
|
3067
3123
|
}
|
|
3068
3124
|
|
|
3069
3125
|
// app/lib/whatsapp/opening-hours.ts
|
|
3070
|
-
var
|
|
3126
|
+
var TAG11 = "[whatsapp:hours]";
|
|
3071
3127
|
async function isBusinessOpen(accountId) {
|
|
3072
3128
|
try {
|
|
3073
3129
|
const timezone = await resolveTimezone(accountId);
|
|
@@ -3085,7 +3141,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3085
3141
|
{ accountId, dayOfWeek, previousDayOfWeek }
|
|
3086
3142
|
);
|
|
3087
3143
|
if (result.records.length === 0) {
|
|
3088
|
-
console.error(`${
|
|
3144
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3089
3145
|
return { open: true, reason: "no opening hours configured" };
|
|
3090
3146
|
}
|
|
3091
3147
|
const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
|
|
@@ -3094,7 +3150,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3094
3150
|
closes: String(r.get("closes")).trim()
|
|
3095
3151
|
}));
|
|
3096
3152
|
if (specs.length === 0) {
|
|
3097
|
-
console.error(`${
|
|
3153
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3098
3154
|
return { open: true, reason: "no opening hours configured" };
|
|
3099
3155
|
}
|
|
3100
3156
|
for (const spec of specs) {
|
|
@@ -3104,7 +3160,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3104
3160
|
if (spec.opens > spec.closes && currentTime < spec.closes) {
|
|
3105
3161
|
const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
|
|
3106
3162
|
console.error(
|
|
3107
|
-
`${
|
|
3163
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
3108
3164
|
);
|
|
3109
3165
|
return {
|
|
3110
3166
|
open: true,
|
|
@@ -3117,7 +3173,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3117
3173
|
} else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
|
|
3118
3174
|
const hoursStr = `${spec.opens}-${spec.closes}`;
|
|
3119
3175
|
console.error(
|
|
3120
|
-
`${
|
|
3176
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
3121
3177
|
);
|
|
3122
3178
|
return {
|
|
3123
3179
|
open: true,
|
|
@@ -3131,7 +3187,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3131
3187
|
const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
|
|
3132
3188
|
const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
|
|
3133
3189
|
console.error(
|
|
3134
|
-
`${
|
|
3190
|
+
`${TAG11} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
|
|
3135
3191
|
);
|
|
3136
3192
|
return {
|
|
3137
3193
|
open: false,
|
|
@@ -3145,7 +3201,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3145
3201
|
}
|
|
3146
3202
|
} catch (err) {
|
|
3147
3203
|
console.error(
|
|
3148
|
-
`${
|
|
3204
|
+
`${TAG11} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
|
|
3149
3205
|
);
|
|
3150
3206
|
return { open: true, reason: "hours check failed (treating as open)" };
|
|
3151
3207
|
}
|
|
@@ -3191,7 +3247,7 @@ import { execFile } from "child_process";
|
|
|
3191
3247
|
import { unlink, stat } from "fs/promises";
|
|
3192
3248
|
import { promisify } from "util";
|
|
3193
3249
|
var execFileAsync = promisify(execFile);
|
|
3194
|
-
var
|
|
3250
|
+
var TAG12 = "[stt]";
|
|
3195
3251
|
var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
|
|
3196
3252
|
var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
|
|
3197
3253
|
var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
@@ -3202,11 +3258,11 @@ async function transcribe(audioPath, mimetype) {
|
|
|
3202
3258
|
const s = await stat(audioPath);
|
|
3203
3259
|
audioBytes = s.size;
|
|
3204
3260
|
} catch {
|
|
3205
|
-
console.error(`${
|
|
3261
|
+
console.error(`${TAG12} failed: file not readable path=${audioPath}`);
|
|
3206
3262
|
return void 0;
|
|
3207
3263
|
}
|
|
3208
3264
|
console.error(
|
|
3209
|
-
`${
|
|
3265
|
+
`${TAG12} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
|
|
3210
3266
|
);
|
|
3211
3267
|
const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
|
|
3212
3268
|
try {
|
|
@@ -3225,7 +3281,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
3225
3281
|
], { timeout: 3e4 });
|
|
3226
3282
|
} catch (err) {
|
|
3227
3283
|
const reason = err instanceof Error ? err.message : String(err);
|
|
3228
|
-
console.error(`${
|
|
3284
|
+
console.error(`${TAG12} failed: ffmpeg conversion error=${reason}`);
|
|
3229
3285
|
return void 0;
|
|
3230
3286
|
}
|
|
3231
3287
|
try {
|
|
@@ -3243,20 +3299,20 @@ async function transcribe(audioPath, mimetype) {
|
|
|
3243
3299
|
const text = stdout.trim();
|
|
3244
3300
|
const durationMs = Date.now() - startMs;
|
|
3245
3301
|
if (!text) {
|
|
3246
|
-
console.error(`${
|
|
3302
|
+
console.error(`${TAG12} failed: whisper returned empty output duration_ms=${durationMs}`);
|
|
3247
3303
|
return void 0;
|
|
3248
3304
|
}
|
|
3249
3305
|
const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
|
|
3250
3306
|
const language = langMatch?.[1] ?? "unknown";
|
|
3251
3307
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
3252
3308
|
console.error(
|
|
3253
|
-
`${
|
|
3309
|
+
`${TAG12} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
|
|
3254
3310
|
);
|
|
3255
3311
|
return { text, language, durationMs };
|
|
3256
3312
|
} catch (err) {
|
|
3257
3313
|
const durationMs = Date.now() - startMs;
|
|
3258
3314
|
const reason = err instanceof Error ? err.message : String(err);
|
|
3259
|
-
console.error(`${
|
|
3315
|
+
console.error(`${TAG12} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
|
|
3260
3316
|
return void 0;
|
|
3261
3317
|
} finally {
|
|
3262
3318
|
unlink(wavPath).catch(() => {
|
|
@@ -3275,7 +3331,7 @@ var WaReplyError = class extends Error {
|
|
|
3275
3331
|
};
|
|
3276
3332
|
|
|
3277
3333
|
// app/lib/whatsapp/manager.ts
|
|
3278
|
-
var
|
|
3334
|
+
var TAG13 = "[whatsapp:manager]";
|
|
3279
3335
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
3280
3336
|
var MESSAGE_STORE_MAX = 500;
|
|
3281
3337
|
var connections = /* @__PURE__ */ new Map();
|
|
@@ -3294,7 +3350,7 @@ function storeMessage(storeKey, entry) {
|
|
|
3294
3350
|
if (entries.length > MESSAGE_STORE_MAX) {
|
|
3295
3351
|
const trimmed = entries.length - MESSAGE_STORE_MAX;
|
|
3296
3352
|
entries.splice(0, trimmed);
|
|
3297
|
-
console.error(`${
|
|
3353
|
+
console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
3298
3354
|
}
|
|
3299
3355
|
}
|
|
3300
3356
|
function deriveCacheKey(input) {
|
|
@@ -3311,9 +3367,28 @@ function deriveCacheKey(input) {
|
|
|
3311
3367
|
}
|
|
3312
3368
|
return `whatsapp:${input.accountId}:${input.senderPhone}`;
|
|
3313
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
|
+
}
|
|
3314
3389
|
async function init(opts) {
|
|
3315
3390
|
if (initialized) {
|
|
3316
|
-
console.error(`${
|
|
3391
|
+
console.error(`${TAG13} already initialized`);
|
|
3317
3392
|
return;
|
|
3318
3393
|
}
|
|
3319
3394
|
configDir = opts.configDir;
|
|
@@ -3321,20 +3396,20 @@ async function init(opts) {
|
|
|
3321
3396
|
loadConfig(opts.accountConfig);
|
|
3322
3397
|
const accountIds = listCredentialAccountIds(configDir);
|
|
3323
3398
|
if (accountIds.length === 0) {
|
|
3324
|
-
console.error(`${
|
|
3399
|
+
console.error(`${TAG13} init: no stored WhatsApp credentials found`);
|
|
3325
3400
|
initialized = true;
|
|
3326
3401
|
return;
|
|
3327
3402
|
}
|
|
3328
|
-
console.error(`${
|
|
3403
|
+
console.error(`${TAG13} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
|
|
3329
3404
|
initialized = true;
|
|
3330
3405
|
for (const accountId of accountIds) {
|
|
3331
3406
|
const accountCfg = whatsAppConfig.accounts?.[accountId];
|
|
3332
3407
|
if (accountCfg?.enabled === false) {
|
|
3333
|
-
console.error(`${
|
|
3408
|
+
console.error(`${TAG13} skipping disabled account=${accountId}`);
|
|
3334
3409
|
continue;
|
|
3335
3410
|
}
|
|
3336
3411
|
startConnection(accountId).catch((err) => {
|
|
3337
|
-
console.error(`${
|
|
3412
|
+
console.error(`${TAG13} failed to auto-start account=${accountId}: ${formatError(err)}`);
|
|
3338
3413
|
});
|
|
3339
3414
|
}
|
|
3340
3415
|
}
|
|
@@ -3343,7 +3418,7 @@ async function startConnection(accountId) {
|
|
|
3343
3418
|
const authDir = resolveAuthDir(configDir, accountId);
|
|
3344
3419
|
const hasAuth = await authExists(authDir);
|
|
3345
3420
|
if (!hasAuth) {
|
|
3346
|
-
console.error(`${
|
|
3421
|
+
console.error(`${TAG13} no credentials for account=${accountId}`);
|
|
3347
3422
|
return;
|
|
3348
3423
|
}
|
|
3349
3424
|
let platformAccountId;
|
|
@@ -3393,11 +3468,11 @@ async function stopConnection(accountId) {
|
|
|
3393
3468
|
conn.sock.ev.removeAllListeners("creds.update");
|
|
3394
3469
|
conn.sock.ws?.close?.();
|
|
3395
3470
|
} catch (err) {
|
|
3396
|
-
console.warn(`${
|
|
3471
|
+
console.warn(`${TAG13} socket cleanup error during stop account=${accountId}: ${String(err)}`);
|
|
3397
3472
|
}
|
|
3398
3473
|
}
|
|
3399
3474
|
connections.delete(accountId);
|
|
3400
|
-
console.error(`${
|
|
3475
|
+
console.error(`${TAG13} stopped account=${accountId}`);
|
|
3401
3476
|
}
|
|
3402
3477
|
function getStatus() {
|
|
3403
3478
|
return Array.from(connections.values()).map((conn) => ({
|
|
@@ -3459,9 +3534,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
3459
3534
|
connections.set(accountId, conn);
|
|
3460
3535
|
try {
|
|
3461
3536
|
await sock.sendPresenceUpdate("available");
|
|
3462
|
-
console.error(`${
|
|
3537
|
+
console.error(`${TAG13} presence set to available account=${accountId}`);
|
|
3463
3538
|
} catch (err) {
|
|
3464
|
-
console.error(`${
|
|
3539
|
+
console.error(`${TAG13} presence update failed account=${accountId}: ${String(err)}`);
|
|
3465
3540
|
}
|
|
3466
3541
|
await runInitQueries(sock, {
|
|
3467
3542
|
accountId,
|
|
@@ -3473,7 +3548,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
3473
3548
|
monitorInbound(conn);
|
|
3474
3549
|
watchForDisconnect(conn);
|
|
3475
3550
|
attachSelfIdRefreshOnCredsUpdate(conn);
|
|
3476
|
-
console.error(`${
|
|
3551
|
+
console.error(`${TAG13} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
3477
3552
|
}
|
|
3478
3553
|
function attachSelfIdRefreshOnCredsUpdate(conn) {
|
|
3479
3554
|
if (!conn.sock) return;
|
|
@@ -3524,7 +3599,7 @@ async function shutdown() {
|
|
|
3524
3599
|
const ids = Array.from(connections.keys());
|
|
3525
3600
|
await Promise.all(ids.map((id) => stopConnection(id)));
|
|
3526
3601
|
initialized = false;
|
|
3527
|
-
console.error(`${
|
|
3602
|
+
console.error(`${TAG13} shutdown complete`);
|
|
3528
3603
|
}
|
|
3529
3604
|
function loadConfig(accountConfig) {
|
|
3530
3605
|
try {
|
|
@@ -3535,19 +3610,19 @@ function loadConfig(accountConfig) {
|
|
|
3535
3610
|
if (result.ok) {
|
|
3536
3611
|
whatsAppConfig = result.data;
|
|
3537
3612
|
if (result.droppedKeys.length > 0) {
|
|
3538
|
-
console.error(`${
|
|
3613
|
+
console.error(`${TAG13} config: dropped unknown keys=[${result.droppedKeys.join(",")}] preserved known config`);
|
|
3539
3614
|
}
|
|
3540
3615
|
} else {
|
|
3541
|
-
console.error(`${
|
|
3616
|
+
console.error(`${TAG13} config validation failed: ${result.error.message}`);
|
|
3542
3617
|
whatsAppConfig = {};
|
|
3543
3618
|
}
|
|
3544
3619
|
const diskAdmin = Array.isArray(wa.adminPhones) ? wa.adminPhones : [];
|
|
3545
3620
|
if (diskAdmin.length > 0 && (whatsAppConfig.adminPhones?.length ?? 0) === 0) {
|
|
3546
|
-
console.error(`${
|
|
3621
|
+
console.error(`${TAG13} WARN adminPhones present on disk but empty after load`);
|
|
3547
3622
|
}
|
|
3548
3623
|
}
|
|
3549
3624
|
} catch (err) {
|
|
3550
|
-
console.error(`${
|
|
3625
|
+
console.error(`${TAG13} config load error: ${String(err)}`);
|
|
3551
3626
|
whatsAppConfig = {};
|
|
3552
3627
|
}
|
|
3553
3628
|
}
|
|
@@ -3558,13 +3633,13 @@ async function connectWithReconnect(conn) {
|
|
|
3558
3633
|
let cycleError = null;
|
|
3559
3634
|
let connectedAt;
|
|
3560
3635
|
try {
|
|
3561
|
-
console.error(`${
|
|
3636
|
+
console.error(`${TAG13} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
|
|
3562
3637
|
const sock = await createWaSocket({
|
|
3563
3638
|
authDir: conn.authDir,
|
|
3564
3639
|
silent: true
|
|
3565
3640
|
});
|
|
3566
3641
|
conn.sock = sock;
|
|
3567
|
-
console.error(`${
|
|
3642
|
+
console.error(`${TAG13} socket created account=${conn.accountId} \u2014 waiting for connection`);
|
|
3568
3643
|
await waitForConnection(sock);
|
|
3569
3644
|
const selfId = readSelfId(conn.authDir);
|
|
3570
3645
|
connectedAt = Date.now();
|
|
@@ -3582,12 +3657,12 @@ async function connectWithReconnect(conn) {
|
|
|
3582
3657
|
} else {
|
|
3583
3658
|
console.error(`[whatsapp:lid] FAIL self-lid-resolution accountId=${conn.accountId} reason=creds-missing-lid`);
|
|
3584
3659
|
}
|
|
3585
|
-
console.error(`${
|
|
3660
|
+
console.error(`${TAG13} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
3586
3661
|
try {
|
|
3587
3662
|
await sock.sendPresenceUpdate("available");
|
|
3588
|
-
console.error(`${
|
|
3663
|
+
console.error(`${TAG13} presence set to available account=${conn.accountId}`);
|
|
3589
3664
|
} catch (err) {
|
|
3590
|
-
console.error(`${
|
|
3665
|
+
console.error(`${TAG13} presence update failed account=${conn.accountId}: ${String(err)}`);
|
|
3591
3666
|
}
|
|
3592
3667
|
await runInitQueries(sock, {
|
|
3593
3668
|
accountId: conn.accountId,
|
|
@@ -3615,10 +3690,10 @@ async function connectWithReconnect(conn) {
|
|
|
3615
3690
|
}
|
|
3616
3691
|
const classification = classifyDisconnect(err);
|
|
3617
3692
|
conn.lastError = classification.message;
|
|
3618
|
-
console.error(`${
|
|
3693
|
+
console.error(`${TAG13} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
|
|
3619
3694
|
if (!classification.shouldRetry) {
|
|
3620
3695
|
conn.terminalReason = classification.message;
|
|
3621
|
-
console.error(`${
|
|
3696
|
+
console.error(`${TAG13} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
|
|
3622
3697
|
return;
|
|
3623
3698
|
}
|
|
3624
3699
|
}
|
|
@@ -3631,7 +3706,7 @@ async function connectWithReconnect(conn) {
|
|
|
3631
3706
|
if (decision.action === "abort") {
|
|
3632
3707
|
const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
|
|
3633
3708
|
console.error(
|
|
3634
|
-
`${
|
|
3709
|
+
`${TAG13} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
|
|
3635
3710
|
);
|
|
3636
3711
|
conn.sessionStuckReason = stuckReason;
|
|
3637
3712
|
conn.lastError = stuckReason;
|
|
@@ -3640,17 +3715,17 @@ async function connectWithReconnect(conn) {
|
|
|
3640
3715
|
conn.reconnectAttempts = decision.nextAttempts;
|
|
3641
3716
|
if (decision.reason === "short-lived") {
|
|
3642
3717
|
console.error(
|
|
3643
|
-
`${
|
|
3718
|
+
`${TAG13} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
|
|
3644
3719
|
);
|
|
3645
3720
|
} else {
|
|
3646
3721
|
console.error(
|
|
3647
|
-
`${
|
|
3722
|
+
`${TAG13} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
|
|
3648
3723
|
);
|
|
3649
3724
|
}
|
|
3650
3725
|
if (decision.reason === "short-lived" || cycleError) {
|
|
3651
3726
|
const delay = computeBackoff(Math.max(1, decision.nextAttempts));
|
|
3652
3727
|
console.error(
|
|
3653
|
-
`${
|
|
3728
|
+
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
3654
3729
|
);
|
|
3655
3730
|
await new Promise((resolve28) => {
|
|
3656
3731
|
const timer2 = setTimeout(resolve28, delay);
|
|
@@ -3684,11 +3759,11 @@ function watchForDisconnect(conn) {
|
|
|
3684
3759
|
conn.sock.ev.on("connection.update", (update) => {
|
|
3685
3760
|
if (update.connection === "close") {
|
|
3686
3761
|
if (connections.get(conn.accountId) !== conn) return;
|
|
3687
|
-
console.error(`${
|
|
3762
|
+
console.error(`${TAG13} socket disconnected for account=${conn.accountId}`);
|
|
3688
3763
|
conn.connected = false;
|
|
3689
3764
|
conn.sock = null;
|
|
3690
3765
|
connectWithReconnect(conn).catch((err) => {
|
|
3691
|
-
console.error(`${
|
|
3766
|
+
console.error(`${TAG13} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
|
|
3692
3767
|
});
|
|
3693
3768
|
}
|
|
3694
3769
|
});
|
|
@@ -3697,7 +3772,7 @@ function monitorInbound(conn) {
|
|
|
3697
3772
|
if (!conn.sock || !onInboundMessage) return;
|
|
3698
3773
|
const sock = conn.sock;
|
|
3699
3774
|
const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
|
|
3700
|
-
console.error(`${
|
|
3775
|
+
console.error(`${TAG13} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
|
|
3701
3776
|
conn.debouncer = createInboundDebouncer({
|
|
3702
3777
|
debounceMs,
|
|
3703
3778
|
buildKey: (payload) => {
|
|
@@ -3707,12 +3782,12 @@ function monitorInbound(conn) {
|
|
|
3707
3782
|
onFlush: (entries) => {
|
|
3708
3783
|
if (!onInboundMessage) return;
|
|
3709
3784
|
if (entries.length > 1) {
|
|
3710
|
-
console.error(`${
|
|
3785
|
+
console.error(`${TAG13} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
|
|
3711
3786
|
}
|
|
3712
3787
|
onInboundMessage(combineInboundBatch(entries));
|
|
3713
3788
|
},
|
|
3714
3789
|
onError: (err) => {
|
|
3715
|
-
console.error(`${
|
|
3790
|
+
console.error(`${TAG13} debounce flush error account=${conn.accountId}: ${String(err)}`);
|
|
3716
3791
|
}
|
|
3717
3792
|
});
|
|
3718
3793
|
sock.ev.on("messages.upsert", async (upsert) => {
|
|
@@ -3740,7 +3815,7 @@ function monitorInbound(conn) {
|
|
|
3740
3815
|
});
|
|
3741
3816
|
const entries = messageStore.get(storeKey);
|
|
3742
3817
|
console.error(
|
|
3743
|
-
`${
|
|
3818
|
+
`${TAG13} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
|
|
3744
3819
|
);
|
|
3745
3820
|
recordActivity({
|
|
3746
3821
|
accountId: conn.accountId,
|
|
@@ -3803,14 +3878,46 @@ function monitorInbound(conn) {
|
|
|
3803
3878
|
isGroupJid(remoteJid) ? msg.key.participant ?? void 0 : void 0
|
|
3804
3879
|
);
|
|
3805
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
|
+
}
|
|
3806
3913
|
console.error(
|
|
3807
|
-
`${
|
|
3914
|
+
`${TAG13} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
|
|
3808
3915
|
);
|
|
3809
3916
|
continue;
|
|
3810
3917
|
}
|
|
3811
3918
|
await handleInboundMessage(conn, msg);
|
|
3812
3919
|
} catch (err) {
|
|
3813
|
-
console.error(`${
|
|
3920
|
+
console.error(`${TAG13} inbound handler error account=${conn.accountId}: ${String(err)}`);
|
|
3814
3921
|
}
|
|
3815
3922
|
}
|
|
3816
3923
|
});
|
|
@@ -3820,31 +3927,31 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3820
3927
|
const remoteJid = msg.key.remoteJid;
|
|
3821
3928
|
if (!remoteJid) return;
|
|
3822
3929
|
if (remoteJid === "status@broadcast") {
|
|
3823
|
-
console.error(`${
|
|
3930
|
+
console.error(`${TAG13} drop: status broadcast account=${conn.accountId}`);
|
|
3824
3931
|
return;
|
|
3825
3932
|
}
|
|
3826
3933
|
if (!msg.message) {
|
|
3827
|
-
console.error(`${
|
|
3934
|
+
console.error(`${TAG13} drop: empty message account=${conn.accountId} from=${remoteJid}`);
|
|
3828
3935
|
return;
|
|
3829
3936
|
}
|
|
3830
3937
|
const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
|
|
3831
3938
|
if (isDuplicateInbound(dedupKey)) {
|
|
3832
|
-
console.error(`${
|
|
3939
|
+
console.error(`${TAG13} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
|
|
3833
3940
|
return;
|
|
3834
3941
|
}
|
|
3835
3942
|
if (msg.key.fromMe) {
|
|
3836
3943
|
if (msg.key.id && isAgentSentMessage(msg.key.id)) {
|
|
3837
|
-
console.error(`${
|
|
3944
|
+
console.error(`${TAG13} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
|
|
3838
3945
|
return;
|
|
3839
3946
|
}
|
|
3840
3947
|
const extracted2 = extractMessage(msg);
|
|
3841
3948
|
if (!extracted2.text) {
|
|
3842
|
-
console.error(`${
|
|
3949
|
+
console.error(`${TAG13} owner reply skipped \u2014 no text content account=${conn.accountId}`);
|
|
3843
3950
|
return;
|
|
3844
3951
|
}
|
|
3845
3952
|
const isGroup2 = isGroupJid(remoteJid);
|
|
3846
3953
|
const senderPhone2 = conn.selfPhone ?? "owner";
|
|
3847
|
-
console.error(`${
|
|
3954
|
+
console.error(`${TAG13} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
|
|
3848
3955
|
const reply2 = async (text) => {
|
|
3849
3956
|
const currentSock = conn.sock;
|
|
3850
3957
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
@@ -3878,7 +3985,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3878
3985
|
}
|
|
3879
3986
|
const extracted = extractMessage(msg);
|
|
3880
3987
|
if (!extracted.text && !extracted.mediaType) {
|
|
3881
|
-
console.error(`${
|
|
3988
|
+
console.error(`${TAG13} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
|
|
3882
3989
|
return;
|
|
3883
3990
|
}
|
|
3884
3991
|
let mediaResult;
|
|
@@ -3888,7 +3995,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3888
3995
|
maxBytes: maxMb * 1024 * 1024
|
|
3889
3996
|
});
|
|
3890
3997
|
if (!mediaResult) {
|
|
3891
|
-
console.error(`${
|
|
3998
|
+
console.error(`${TAG13} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
3892
3999
|
}
|
|
3893
4000
|
}
|
|
3894
4001
|
const isGroup = isGroupJid(remoteJid);
|
|
@@ -3926,7 +4033,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3926
4033
|
});
|
|
3927
4034
|
}
|
|
3928
4035
|
console.error(
|
|
3929
|
-
`${
|
|
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}` : "")
|
|
3930
4037
|
);
|
|
3931
4038
|
if (!accessResult.allowed) return;
|
|
3932
4039
|
const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
|
|
@@ -3935,7 +4042,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3935
4042
|
}
|
|
3936
4043
|
const reply = async (text) => {
|
|
3937
4044
|
if (isGroupJid(remoteJid)) {
|
|
3938
|
-
console.error(`${
|
|
4045
|
+
console.error(`${TAG13} op=group-reply-blocked jid=${remoteJid}`);
|
|
3939
4046
|
throw new WaReplyError("group reply blocked \u2014 observe-only", false);
|
|
3940
4047
|
}
|
|
3941
4048
|
const currentSock = conn.sock;
|
|
@@ -3973,15 +4080,15 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3973
4080
|
if (accessResult.agentType === "public") {
|
|
3974
4081
|
const hoursResult = await isBusinessOpen(conn.accountId);
|
|
3975
4082
|
if (!hoursResult.open) {
|
|
3976
|
-
console.error(`${
|
|
4083
|
+
console.error(`${TAG13} [${conn.accountId}] dispatch skipped: business closed`);
|
|
3977
4084
|
const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
|
|
3978
4085
|
if (afterHoursMessage) {
|
|
3979
4086
|
try {
|
|
3980
4087
|
await reply(afterHoursMessage);
|
|
3981
|
-
console.error(`${
|
|
4088
|
+
console.error(`${TAG13} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
|
|
3982
4089
|
} catch (err) {
|
|
3983
4090
|
console.error(
|
|
3984
|
-
`${
|
|
4091
|
+
`${TAG13} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3985
4092
|
);
|
|
3986
4093
|
}
|
|
3987
4094
|
}
|
|
@@ -4495,7 +4602,7 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
|
|
|
4495
4602
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
4496
4603
|
import { tmpdir } from "os";
|
|
4497
4604
|
import { join as join3 } from "path";
|
|
4498
|
-
var
|
|
4605
|
+
var TAG14 = "[voice]";
|
|
4499
4606
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
4500
4607
|
"audio/ogg",
|
|
4501
4608
|
"audio/opus",
|
|
@@ -4533,7 +4640,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4533
4640
|
const bytes = file.size;
|
|
4534
4641
|
const mimeType = file.type;
|
|
4535
4642
|
console.error(
|
|
4536
|
-
`${
|
|
4643
|
+
`${TAG14} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
|
|
4537
4644
|
);
|
|
4538
4645
|
let tempDir;
|
|
4539
4646
|
let tempPath;
|
|
@@ -4545,7 +4652,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4545
4652
|
await writeFile3(tempPath, buffer);
|
|
4546
4653
|
} catch (err) {
|
|
4547
4654
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4548
|
-
console.error(`${
|
|
4655
|
+
console.error(`${TAG14} failed source=${source} error=temp-file-write: ${reason}`);
|
|
4549
4656
|
return { ok: false, error: "Could not process voice note" };
|
|
4550
4657
|
}
|
|
4551
4658
|
try {
|
|
@@ -4553,7 +4660,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4553
4660
|
if (!sttResult) {
|
|
4554
4661
|
const elapsed2 = Date.now() - startMs;
|
|
4555
4662
|
console.error(
|
|
4556
|
-
`${
|
|
4663
|
+
`${TAG14} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
|
|
4557
4664
|
);
|
|
4558
4665
|
return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
|
|
4559
4666
|
}
|
|
@@ -4561,7 +4668,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4561
4668
|
const elapsed = Date.now() - startMs;
|
|
4562
4669
|
const words = rawText.split(/\s+/).filter(Boolean).length;
|
|
4563
4670
|
console.error(
|
|
4564
|
-
`${
|
|
4671
|
+
`${TAG14} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
|
|
4565
4672
|
);
|
|
4566
4673
|
return {
|
|
4567
4674
|
ok: true,
|
|
@@ -4571,7 +4678,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
4571
4678
|
const elapsed = Date.now() - startMs;
|
|
4572
4679
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4573
4680
|
console.error(
|
|
4574
|
-
`${
|
|
4681
|
+
`${TAG14} failed source=${source} error=${reason} duration_ms=${elapsed}`
|
|
4575
4682
|
);
|
|
4576
4683
|
return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
|
|
4577
4684
|
} finally {
|
|
@@ -4640,6 +4747,7 @@ async function managerRcSpawn(opts) {
|
|
|
4640
4747
|
personId: opts.personId,
|
|
4641
4748
|
userId: opts.userId,
|
|
4642
4749
|
waChannel: opts.waChannel,
|
|
4750
|
+
webchatChannel: opts.webchatChannel,
|
|
4643
4751
|
role: opts.role,
|
|
4644
4752
|
channel: opts.channel,
|
|
4645
4753
|
senderId: opts.senderId
|
|
@@ -4711,7 +4819,7 @@ function adminSessionIdFor(accountId, senderId) {
|
|
|
4711
4819
|
|
|
4712
4820
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
4713
4821
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4714
|
-
var
|
|
4822
|
+
var TAG15 = "[public-session-review]";
|
|
4715
4823
|
var CONSUMED_CAP = 500;
|
|
4716
4824
|
var consumed = /* @__PURE__ */ new Set();
|
|
4717
4825
|
function consumeOnce(sessionId) {
|
|
@@ -4738,7 +4846,7 @@ async function fetchJsonl(sessionId) {
|
|
|
4738
4846
|
return await res.text();
|
|
4739
4847
|
} catch (err) {
|
|
4740
4848
|
console.error(
|
|
4741
|
-
`${
|
|
4849
|
+
`${TAG15} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
4742
4850
|
);
|
|
4743
4851
|
return null;
|
|
4744
4852
|
}
|
|
@@ -4762,7 +4870,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
4762
4870
|
const res = await fetch(url);
|
|
4763
4871
|
if (!res.ok) {
|
|
4764
4872
|
console.error(
|
|
4765
|
-
`${
|
|
4873
|
+
`${TAG15} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
4766
4874
|
);
|
|
4767
4875
|
return [];
|
|
4768
4876
|
}
|
|
@@ -4770,7 +4878,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
4770
4878
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
4771
4879
|
} catch (err) {
|
|
4772
4880
|
console.error(
|
|
4773
|
-
`${
|
|
4881
|
+
`${TAG15} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
4774
4882
|
);
|
|
4775
4883
|
return [];
|
|
4776
4884
|
}
|
|
@@ -4799,7 +4907,7 @@ function composeInitialMessage(input) {
|
|
|
4799
4907
|
}
|
|
4800
4908
|
async function dispatchReviewer(input, initialMessage) {
|
|
4801
4909
|
const sessionId = randomUUID4();
|
|
4802
|
-
console.log(`${
|
|
4910
|
+
console.log(`${TAG15} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
4803
4911
|
const spawned = await managerRcSpawn({
|
|
4804
4912
|
sessionId,
|
|
4805
4913
|
initialMessage,
|
|
@@ -4819,21 +4927,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
4819
4927
|
const dispatchedAt = Date.now();
|
|
4820
4928
|
if (consumed.has(input.sessionId)) {
|
|
4821
4929
|
console.log(
|
|
4822
|
-
`${
|
|
4930
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
4823
4931
|
);
|
|
4824
4932
|
return;
|
|
4825
4933
|
}
|
|
4826
4934
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
4827
4935
|
if (!jsonl) {
|
|
4828
4936
|
console.log(
|
|
4829
|
-
`${
|
|
4937
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
4830
4938
|
);
|
|
4831
4939
|
return;
|
|
4832
4940
|
}
|
|
4833
4941
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
4834
4942
|
if (operatorTurns === 0) {
|
|
4835
4943
|
console.log(
|
|
4836
|
-
`${
|
|
4944
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
4837
4945
|
);
|
|
4838
4946
|
return;
|
|
4839
4947
|
}
|
|
@@ -4847,19 +4955,19 @@ async function firePublicSessionEndReview(input) {
|
|
|
4847
4955
|
});
|
|
4848
4956
|
if (!consumeOnce(input.sessionId)) {
|
|
4849
4957
|
console.log(
|
|
4850
|
-
`${
|
|
4958
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
4851
4959
|
);
|
|
4852
4960
|
return;
|
|
4853
4961
|
}
|
|
4854
4962
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
4855
4963
|
if (!dispatched.ok) {
|
|
4856
4964
|
console.error(
|
|
4857
|
-
`${
|
|
4965
|
+
`${TAG15} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
4858
4966
|
);
|
|
4859
4967
|
return;
|
|
4860
4968
|
}
|
|
4861
4969
|
console.log(
|
|
4862
|
-
`${
|
|
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}`
|
|
4863
4971
|
);
|
|
4864
4972
|
}
|
|
4865
4973
|
|
|
@@ -4952,22 +5060,27 @@ function startFollower(opts) {
|
|
|
4952
5060
|
if (event.type !== "assistant") continue;
|
|
4953
5061
|
const msg = event.message;
|
|
4954
5062
|
if (!msg) continue;
|
|
5063
|
+
let sawContent = false;
|
|
4955
5064
|
if (Array.isArray(msg.content)) {
|
|
4956
5065
|
for (const block of msg.content) {
|
|
4957
5066
|
if (block?.type === "text" && typeof block.text === "string") {
|
|
4958
5067
|
entry.pendingTurnText += block.text;
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
)
|
|
5068
|
+
sawContent = true;
|
|
5069
|
+
} else if (block?.type === "tool_use") {
|
|
5070
|
+
sawContent = true;
|
|
5071
|
+
if (fileDelivery && typeof block.name === "string" && fileDelivery.isFileDeliveryTool(block.name)) {
|
|
5072
|
+
if (suppressing) {
|
|
5073
|
+
discardedFileTools += 1;
|
|
5074
|
+
continue;
|
|
5075
|
+
}
|
|
5076
|
+
firedFileTools.push(block.name);
|
|
5077
|
+
try {
|
|
5078
|
+
await fileDelivery.onFileToolUse({ toolName: block.name, input: block.input });
|
|
5079
|
+
} catch (err) {
|
|
5080
|
+
console.error(
|
|
5081
|
+
`${tag} file-delivery-error sessionId=${sid} tool=${block.name} message=${err instanceof Error ? err.message : String(err)}`
|
|
5082
|
+
);
|
|
5083
|
+
}
|
|
4971
5084
|
}
|
|
4972
5085
|
}
|
|
4973
5086
|
}
|
|
@@ -4995,6 +5108,9 @@ function startFollower(opts) {
|
|
|
4995
5108
|
);
|
|
4996
5109
|
}
|
|
4997
5110
|
}
|
|
5111
|
+
opts.onTurnComplete?.();
|
|
5112
|
+
} else if (sawContent && !suppressing) {
|
|
5113
|
+
opts.onActivity?.();
|
|
4998
5114
|
}
|
|
4999
5115
|
}
|
|
5000
5116
|
}
|
|
@@ -5025,21 +5141,21 @@ async function fanOut(subscribers, text, onError, tag) {
|
|
|
5025
5141
|
import { realpathSync as realpathSync2 } from "fs";
|
|
5026
5142
|
import { readFile, stat as stat2 } from "fs/promises";
|
|
5027
5143
|
import { resolve as resolve4, basename } from "path";
|
|
5028
|
-
var
|
|
5144
|
+
var TAG16 = "[whatsapp:outbound]";
|
|
5029
5145
|
var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
|
|
5030
5146
|
var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
5031
5147
|
var lastRouteDocumentOutboundAt = /* @__PURE__ */ new Map();
|
|
5032
|
-
function
|
|
5148
|
+
function normalizeJid2(to) {
|
|
5033
5149
|
return to.includes("@") ? to : toWhatsappJid(to);
|
|
5034
5150
|
}
|
|
5035
5151
|
function routeKey(to, filePath) {
|
|
5036
|
-
return `${
|
|
5152
|
+
return `${normalizeJid2(to)}\0${filePath}`;
|
|
5037
5153
|
}
|
|
5038
5154
|
function documentOutboundAt(to) {
|
|
5039
|
-
return lastDocumentOutboundAt.get(
|
|
5155
|
+
return lastDocumentOutboundAt.get(normalizeJid2(to));
|
|
5040
5156
|
}
|
|
5041
5157
|
function recordDocumentOutbound(to) {
|
|
5042
|
-
lastDocumentOutboundAt.set(
|
|
5158
|
+
lastDocumentOutboundAt.set(normalizeJid2(to), Date.now());
|
|
5043
5159
|
}
|
|
5044
5160
|
function recordRouteDocumentOutbound(to, filePath) {
|
|
5045
5161
|
lastRouteDocumentOutboundAt.set(routeKey(to, filePath), Date.now());
|
|
@@ -5062,16 +5178,16 @@ async function sendWhatsAppDocument(input) {
|
|
|
5062
5178
|
const accountResolved = realpathSync2(accountDir);
|
|
5063
5179
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
5064
5180
|
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
5065
|
-
console.error(`${
|
|
5181
|
+
console.error(`${TAG16} document REJECTED path=${sanitised} reason=outside_account_directory`);
|
|
5066
5182
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
5067
5183
|
}
|
|
5068
5184
|
} catch (err) {
|
|
5069
5185
|
const code = err.code;
|
|
5070
5186
|
if (code === "ENOENT") {
|
|
5071
|
-
console.error(`${
|
|
5187
|
+
console.error(`${TAG16} document ENOENT path=${filePath}`);
|
|
5072
5188
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
5073
5189
|
}
|
|
5074
|
-
console.error(`${
|
|
5190
|
+
console.error(`${TAG16} document path error: ${String(err)}`);
|
|
5075
5191
|
return { ok: false, status: 500, error: String(err) };
|
|
5076
5192
|
}
|
|
5077
5193
|
const fileStat = await stat2(resolvedPath);
|
|
@@ -5083,10 +5199,10 @@ async function sendWhatsAppDocument(input) {
|
|
|
5083
5199
|
};
|
|
5084
5200
|
}
|
|
5085
5201
|
const filename = basename(resolvedPath);
|
|
5086
|
-
const jid =
|
|
5202
|
+
const jid = normalizeJid2(to);
|
|
5087
5203
|
const sock = getSocket(accountId);
|
|
5088
5204
|
if (!sock) {
|
|
5089
|
-
console.error(`${
|
|
5205
|
+
console.error(`${TAG16} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=false reason=not-connected`);
|
|
5090
5206
|
return { ok: false, status: 503, error: `WhatsApp account "${accountId}" is not connected` };
|
|
5091
5207
|
}
|
|
5092
5208
|
const buffer = Buffer.from(await readFile(resolvedPath));
|
|
@@ -5098,7 +5214,7 @@ async function sendWhatsAppDocument(input) {
|
|
|
5098
5214
|
{ accountId }
|
|
5099
5215
|
);
|
|
5100
5216
|
console.error(
|
|
5101
|
-
`${
|
|
5217
|
+
`${TAG16} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
|
|
5102
5218
|
);
|
|
5103
5219
|
if (result.success) {
|
|
5104
5220
|
recordDocumentOutbound(to);
|
|
@@ -5108,7 +5224,7 @@ async function sendWhatsAppDocument(input) {
|
|
|
5108
5224
|
}
|
|
5109
5225
|
|
|
5110
5226
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
5111
|
-
var
|
|
5227
|
+
var TAG17 = "[whatsapp-adaptor]";
|
|
5112
5228
|
var SEND_USER_FILE = "SendUserFile";
|
|
5113
5229
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
5114
5230
|
function platformRoot() {
|
|
@@ -5143,7 +5259,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
5143
5259
|
maxyAccountId = resolvePlatformAccountId();
|
|
5144
5260
|
} catch (err) {
|
|
5145
5261
|
console.error(
|
|
5146
|
-
`${
|
|
5262
|
+
`${TAG17} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
5147
5263
|
);
|
|
5148
5264
|
return;
|
|
5149
5265
|
}
|
|
@@ -5160,7 +5276,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
5160
5276
|
if (!result.ok) {
|
|
5161
5277
|
failedFiles.push(files[i]);
|
|
5162
5278
|
console.error(
|
|
5163
|
-
`${
|
|
5279
|
+
`${TAG17} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
5164
5280
|
);
|
|
5165
5281
|
}
|
|
5166
5282
|
}
|
|
@@ -5177,7 +5293,7 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
5177
5293
|
const sid = entry.sessionId.slice(0, 8);
|
|
5178
5294
|
for (const file of failed) {
|
|
5179
5295
|
console.error(
|
|
5180
|
-
`${
|
|
5296
|
+
`${TAG17} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
|
|
5181
5297
|
);
|
|
5182
5298
|
}
|
|
5183
5299
|
const okAt = documentOutboundAt(entry.senderId);
|
|
@@ -5188,13 +5304,13 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
5188
5304
|
if (!delivered) {
|
|
5189
5305
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
5190
5306
|
console.error(
|
|
5191
|
-
`${
|
|
5307
|
+
`${TAG17} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
5192
5308
|
);
|
|
5193
5309
|
}
|
|
5194
5310
|
}
|
|
5195
5311
|
if (firedTools.includes(SEND_USER_FILE) && attempts === 0 && !documentWentOut) {
|
|
5196
5312
|
console.error(
|
|
5197
|
-
`${
|
|
5313
|
+
`${TAG17} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
|
|
5198
5314
|
);
|
|
5199
5315
|
}
|
|
5200
5316
|
}
|
|
@@ -5776,7 +5892,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
5776
5892
|
// app/lib/whatsapp/config-persist.ts
|
|
5777
5893
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
|
|
5778
5894
|
import { resolve as resolve6, join as join4 } from "path";
|
|
5779
|
-
var
|
|
5895
|
+
var TAG18 = "[whatsapp:config]";
|
|
5780
5896
|
function configPath(accountDir) {
|
|
5781
5897
|
return resolve6(accountDir, "account.json");
|
|
5782
5898
|
}
|
|
@@ -5793,9 +5909,9 @@ function reloadManagerConfig(accountDir) {
|
|
|
5793
5909
|
try {
|
|
5794
5910
|
const config = readConfig(accountDir);
|
|
5795
5911
|
reloadConfig(config);
|
|
5796
|
-
console.error(`${
|
|
5912
|
+
console.error(`${TAG18} reloaded manager config`);
|
|
5797
5913
|
} catch (err) {
|
|
5798
|
-
console.error(`${
|
|
5914
|
+
console.error(`${TAG18} manager config reload failed: ${String(err)}`);
|
|
5799
5915
|
}
|
|
5800
5916
|
}
|
|
5801
5917
|
var E164_PATTERN = /^\+\d{7,15}$/;
|
|
@@ -5821,25 +5937,25 @@ function persistAfterPairing(accountDir, accountId, selfPhone) {
|
|
|
5821
5937
|
const adminPhones = wa.adminPhones;
|
|
5822
5938
|
if (!adminPhones.includes(normalized)) {
|
|
5823
5939
|
adminPhones.push(normalized);
|
|
5824
|
-
console.error(`${
|
|
5940
|
+
console.error(`${TAG18} added selfPhone=${normalized} to adminPhones`);
|
|
5825
5941
|
}
|
|
5826
5942
|
} else {
|
|
5827
|
-
console.error(`${
|
|
5943
|
+
console.error(`${TAG18} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
|
|
5828
5944
|
}
|
|
5829
5945
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5830
5946
|
if (!parsed.success) {
|
|
5831
5947
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5832
|
-
console.error(`${
|
|
5948
|
+
console.error(`${TAG18} validation failed after pairing: ${msg}`);
|
|
5833
5949
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5834
5950
|
}
|
|
5835
5951
|
config.whatsapp = parsed.data;
|
|
5836
5952
|
writeConfig(accountDir, config);
|
|
5837
|
-
console.error(`${
|
|
5953
|
+
console.error(`${TAG18} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
|
|
5838
5954
|
reloadManagerConfig(accountDir);
|
|
5839
5955
|
return { ok: true };
|
|
5840
5956
|
} catch (err) {
|
|
5841
5957
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5842
|
-
console.error(`${
|
|
5958
|
+
console.error(`${TAG18} persist failed account=${accountId}: ${msg}`);
|
|
5843
5959
|
return { ok: false, error: msg };
|
|
5844
5960
|
}
|
|
5845
5961
|
}
|
|
@@ -5869,12 +5985,12 @@ function addAdminPhone(accountDir, phone) {
|
|
|
5869
5985
|
}
|
|
5870
5986
|
config.whatsapp = parsed.data;
|
|
5871
5987
|
writeConfig(accountDir, config);
|
|
5872
|
-
console.error(`${
|
|
5988
|
+
console.error(`${TAG18} added admin phone=${normalized}`);
|
|
5873
5989
|
reloadManagerConfig(accountDir);
|
|
5874
5990
|
return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
|
|
5875
5991
|
} catch (err) {
|
|
5876
5992
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5877
|
-
console.error(`${
|
|
5993
|
+
console.error(`${TAG18} addAdminPhone failed: ${msg}`);
|
|
5878
5994
|
return { ok: false, error: msg };
|
|
5879
5995
|
}
|
|
5880
5996
|
}
|
|
@@ -5902,12 +6018,12 @@ function removeAdminPhone(accountDir, phone) {
|
|
|
5902
6018
|
}
|
|
5903
6019
|
config.whatsapp = parsed.data;
|
|
5904
6020
|
writeConfig(accountDir, config);
|
|
5905
|
-
console.error(`${
|
|
6021
|
+
console.error(`${TAG18} removed admin phone=${normalized}`);
|
|
5906
6022
|
reloadManagerConfig(accountDir);
|
|
5907
6023
|
return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
|
|
5908
6024
|
} catch (err) {
|
|
5909
6025
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5910
|
-
console.error(`${
|
|
6026
|
+
console.error(`${TAG18} removeAdminPhone failed: ${msg}`);
|
|
5911
6027
|
return { ok: false, error: msg };
|
|
5912
6028
|
}
|
|
5913
6029
|
}
|
|
@@ -5945,12 +6061,12 @@ function setPublicAgent(accountDir, slug) {
|
|
|
5945
6061
|
}
|
|
5946
6062
|
config.whatsapp = parsed.data;
|
|
5947
6063
|
writeConfig(accountDir, config);
|
|
5948
|
-
console.error(`${
|
|
6064
|
+
console.error(`${TAG18} publicAgent set to ${trimmed}`);
|
|
5949
6065
|
reloadManagerConfig(accountDir);
|
|
5950
6066
|
return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
|
|
5951
6067
|
} catch (err) {
|
|
5952
6068
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5953
|
-
console.error(`${
|
|
6069
|
+
console.error(`${TAG18} setPublicAgent failed: ${msg}`);
|
|
5954
6070
|
return { ok: false, error: msg };
|
|
5955
6071
|
}
|
|
5956
6072
|
}
|
|
@@ -5991,17 +6107,17 @@ function updateConfig(accountDir, fields) {
|
|
|
5991
6107
|
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
5992
6108
|
if (!parsed.success) {
|
|
5993
6109
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5994
|
-
console.error(`${
|
|
6110
|
+
console.error(`${TAG18} update validation failed: ${msg}`);
|
|
5995
6111
|
return { ok: false, error: `Validation failed: ${msg}` };
|
|
5996
6112
|
}
|
|
5997
6113
|
config.whatsapp = parsed.data;
|
|
5998
6114
|
writeConfig(accountDir, config);
|
|
5999
|
-
console.error(`${
|
|
6115
|
+
console.error(`${TAG18} updated fields=[${fieldNames.join(",")}]`);
|
|
6000
6116
|
reloadManagerConfig(accountDir);
|
|
6001
6117
|
return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
|
|
6002
6118
|
} catch (err) {
|
|
6003
6119
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6004
|
-
console.error(`${
|
|
6120
|
+
console.error(`${TAG18} updateConfig failed: ${msg}`);
|
|
6005
6121
|
return { ok: false, error: msg };
|
|
6006
6122
|
}
|
|
6007
6123
|
}
|
|
@@ -6038,17 +6154,17 @@ function migrateRemovedConfigKeys(accountDir) {
|
|
|
6038
6154
|
}
|
|
6039
6155
|
writeConfig(accountDir, config);
|
|
6040
6156
|
console.error(
|
|
6041
|
-
`${
|
|
6157
|
+
`${TAG18} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
|
|
6042
6158
|
);
|
|
6043
6159
|
return { dropped: result.droppedKeys };
|
|
6044
6160
|
} catch (err) {
|
|
6045
|
-
console.error(`${
|
|
6161
|
+
console.error(`${TAG18} migration failed: ${String(err)}`);
|
|
6046
6162
|
return { dropped: [] };
|
|
6047
6163
|
}
|
|
6048
6164
|
}
|
|
6049
6165
|
|
|
6050
6166
|
// app/lib/whatsapp/login.ts
|
|
6051
|
-
var
|
|
6167
|
+
var TAG19 = "[whatsapp:login]";
|
|
6052
6168
|
function maskPhone(digits) {
|
|
6053
6169
|
if (digits.length <= 4) return "*".repeat(digits.length);
|
|
6054
6170
|
return digits.slice(0, 2) + "*".repeat(digits.length - 4) + digits.slice(-2);
|
|
@@ -6059,7 +6175,7 @@ function closeSocket(sock) {
|
|
|
6059
6175
|
try {
|
|
6060
6176
|
sock.ws?.close?.();
|
|
6061
6177
|
} catch (err) {
|
|
6062
|
-
console.warn(`${
|
|
6178
|
+
console.warn(`${TAG19} socket close error during cleanup: ${String(err)}`);
|
|
6063
6179
|
}
|
|
6064
6180
|
}
|
|
6065
6181
|
function resetActiveLogin(accountId) {
|
|
@@ -6086,18 +6202,18 @@ async function runPostPairing(login) {
|
|
|
6086
6202
|
const masked = selfPhone ? `${selfPhone.slice(0, 4)}***` : "null";
|
|
6087
6203
|
console.error(`[whatsapp-persist] wa-persist-on-connect account=${login.accountId} phone=${masked}`);
|
|
6088
6204
|
} else {
|
|
6089
|
-
console.error(`${
|
|
6205
|
+
console.error(`${TAG19} wa-persist-on-connect FAILED account=${login.accountId}: ${result.error}`);
|
|
6090
6206
|
}
|
|
6091
6207
|
} catch (err) {
|
|
6092
|
-
console.error(`${
|
|
6208
|
+
console.error(`${TAG19} wa-persist-on-connect error account=${login.accountId}: ${String(err)}`);
|
|
6093
6209
|
}
|
|
6094
6210
|
} else {
|
|
6095
|
-
console.error(`${
|
|
6211
|
+
console.error(`${TAG19} wa-persist-on-connect skipped \u2014 no accountDir account=${login.accountId}`);
|
|
6096
6212
|
}
|
|
6097
6213
|
try {
|
|
6098
6214
|
await registerLoginSocket(login.accountId, login.sock, login.authDir);
|
|
6099
6215
|
} catch (err) {
|
|
6100
|
-
console.error(`${
|
|
6216
|
+
console.error(`${TAG19} registerLoginSocket failed account=${login.accountId}: ${String(err)}`);
|
|
6101
6217
|
}
|
|
6102
6218
|
}
|
|
6103
6219
|
async function loginConnectionLoop(accountId, login) {
|
|
@@ -6109,7 +6225,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6109
6225
|
if (current?.id === login.id) {
|
|
6110
6226
|
await runPostPairing(current);
|
|
6111
6227
|
current.connected = true;
|
|
6112
|
-
console.error(`${
|
|
6228
|
+
console.error(`${TAG19} loginConnectionLoop: connected account=${accountId} attempt=${attempt} configPersisted=${current.configPersisted}`);
|
|
6113
6229
|
}
|
|
6114
6230
|
return;
|
|
6115
6231
|
} catch (err) {
|
|
@@ -6119,7 +6235,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6119
6235
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6120
6236
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6121
6237
|
console.error(
|
|
6122
|
-
`${
|
|
6238
|
+
`${TAG19} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
6123
6239
|
);
|
|
6124
6240
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
6125
6241
|
} else {
|
|
@@ -6131,7 +6247,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6131
6247
|
attempt++;
|
|
6132
6248
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
6133
6249
|
console.error(
|
|
6134
|
-
`${
|
|
6250
|
+
`${TAG19} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
6135
6251
|
);
|
|
6136
6252
|
closeSocket(current.sock);
|
|
6137
6253
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -6142,7 +6258,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6142
6258
|
current.sock = newSock;
|
|
6143
6259
|
} catch (sockErr) {
|
|
6144
6260
|
console.error(
|
|
6145
|
-
`${
|
|
6261
|
+
`${TAG19} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
6146
6262
|
);
|
|
6147
6263
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
6148
6264
|
return;
|
|
@@ -6156,7 +6272,7 @@ async function startLogin(opts) {
|
|
|
6156
6272
|
const hasAuth = await authExists(authDir);
|
|
6157
6273
|
const selfId = readSelfId(authDir);
|
|
6158
6274
|
console.error(
|
|
6159
|
-
`${
|
|
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")
|
|
6160
6276
|
);
|
|
6161
6277
|
if (hasAuth && !force) {
|
|
6162
6278
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -6171,7 +6287,7 @@ async function startLogin(opts) {
|
|
|
6171
6287
|
}
|
|
6172
6288
|
const existing = activeLogins.get(accountId);
|
|
6173
6289
|
if (existing && isLoginFresh(existing) && existing.pairingCode && !force) {
|
|
6174
|
-
console.error(`${
|
|
6290
|
+
console.error(`${TAG19} startLogin account=${accountId} guard: returning existing pairing code (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
6175
6291
|
return {
|
|
6176
6292
|
pairingCode: existing.pairingCode,
|
|
6177
6293
|
phone: existing.phone,
|
|
@@ -6179,7 +6295,7 @@ async function startLogin(opts) {
|
|
|
6179
6295
|
};
|
|
6180
6296
|
}
|
|
6181
6297
|
if (existing) {
|
|
6182
|
-
console.error(`${
|
|
6298
|
+
console.error(`${TAG19} startLogin account=${accountId} ${force ? "force override" : "stale/no-code"}, resetting active login`);
|
|
6183
6299
|
}
|
|
6184
6300
|
resetActiveLogin(accountId);
|
|
6185
6301
|
await clearAuth(authDir);
|
|
@@ -6209,7 +6325,7 @@ async function startLogin(opts) {
|
|
|
6209
6325
|
const current = activeLogins.get(accountId);
|
|
6210
6326
|
if (current?.id !== login.id) return;
|
|
6211
6327
|
if (!current.pairingCode) current.pairingCode = code;
|
|
6212
|
-
console.error(`${
|
|
6328
|
+
console.error(`${TAG19} pairing-code-issued account=${accountId} phone=${maskPhone(digits)} codeLen=${code.length}`);
|
|
6213
6329
|
resolveCode?.(code);
|
|
6214
6330
|
}).catch((err) => {
|
|
6215
6331
|
clearTimeout(codeTimer);
|
|
@@ -6238,7 +6354,7 @@ async function startLogin(opts) {
|
|
|
6238
6354
|
const ttlTimer = setTimeout(() => {
|
|
6239
6355
|
const current = activeLogins.get(accountId);
|
|
6240
6356
|
if (current?.id === login.id && !current.connected) {
|
|
6241
|
-
console.error(`${
|
|
6357
|
+
console.error(`${TAG19} pairing-window-elapsed account=${accountId} (ttl sweep)`);
|
|
6242
6358
|
resetActiveLogin(accountId);
|
|
6243
6359
|
}
|
|
6244
6360
|
}, ACTIVE_LOGIN_TTL_MS);
|
|
@@ -6246,7 +6362,7 @@ async function startLogin(opts) {
|
|
|
6246
6362
|
ttlTimer.unref();
|
|
6247
6363
|
}
|
|
6248
6364
|
loginConnectionLoop(accountId, login).catch((err) => {
|
|
6249
|
-
console.error(`${
|
|
6365
|
+
console.error(`${TAG19} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
6250
6366
|
const current = activeLogins.get(accountId);
|
|
6251
6367
|
if (current?.id === login.id) {
|
|
6252
6368
|
current.error = `Unexpected login error: ${String(err)}`;
|
|
@@ -6271,13 +6387,13 @@ async function waitForLogin(opts) {
|
|
|
6271
6387
|
const { accountId, timeoutMs = 6e4 } = opts;
|
|
6272
6388
|
const login = activeLogins.get(accountId);
|
|
6273
6389
|
console.error(
|
|
6274
|
-
`${
|
|
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")
|
|
6275
6391
|
);
|
|
6276
6392
|
if (!login) {
|
|
6277
6393
|
return { connected: false, message: "No active WhatsApp login in progress.", configPersisted: false };
|
|
6278
6394
|
}
|
|
6279
6395
|
if (!isLoginFresh(login)) {
|
|
6280
|
-
console.error(`${
|
|
6396
|
+
console.error(`${TAG19} pairing-window-elapsed account=${accountId}`);
|
|
6281
6397
|
resetActiveLogin(accountId);
|
|
6282
6398
|
return { connected: false, message: "The pairing window expired. Ask me to generate a new code.", configPersisted: false };
|
|
6283
6399
|
}
|
|
@@ -6285,8 +6401,8 @@ async function waitForLogin(opts) {
|
|
|
6285
6401
|
while (Date.now() < deadline) {
|
|
6286
6402
|
if (login.connected) {
|
|
6287
6403
|
const selfId = readSelfId(login.authDir);
|
|
6288
|
-
console.error(`${
|
|
6289
|
-
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}`);
|
|
6290
6406
|
const configPersisted = login.configPersisted;
|
|
6291
6407
|
activeLogins.delete(accountId);
|
|
6292
6408
|
return {
|
|
@@ -6304,7 +6420,7 @@ async function waitForLogin(opts) {
|
|
|
6304
6420
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
6305
6421
|
}
|
|
6306
6422
|
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
6307
|
-
console.error(`${
|
|
6423
|
+
console.error(`${TAG19} waitForLogin poll timeout account=${accountId} elapsed=${elapsed}s \u2014 login kept alive (pairing code still valid)`);
|
|
6308
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 };
|
|
6309
6425
|
}
|
|
6310
6426
|
|
|
@@ -6419,7 +6535,7 @@ function serializeWhatsAppSchema() {
|
|
|
6419
6535
|
// app/lib/whatsapp/status-reconcile.ts
|
|
6420
6536
|
import { readdirSync as readdirSync2 } from "fs";
|
|
6421
6537
|
import { join as join5 } from "path";
|
|
6422
|
-
var
|
|
6538
|
+
var TAG20 = "[whatsapp:reconcile]";
|
|
6423
6539
|
function listCredsAccountIds(credsRoot) {
|
|
6424
6540
|
try {
|
|
6425
6541
|
return readdirSync2(credsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && hasCredsSync(join5(credsRoot, e.name))).map((e) => e.name);
|
|
@@ -6441,14 +6557,14 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6441
6557
|
const selfPhone = readSelfId(authDir).e164 ?? void 0;
|
|
6442
6558
|
const configured = accountDir ? isAccountConfigured(accountDir, accountId) : false;
|
|
6443
6559
|
console.error(
|
|
6444
|
-
`${
|
|
6560
|
+
`${TAG20} wa-link-unconfigured account=${accountId} creds=present config=${configured ? "present" : "absent"}`
|
|
6445
6561
|
);
|
|
6446
6562
|
if (!configured && accountDir) {
|
|
6447
6563
|
const result = persistAfterPairing(accountDir, accountId, selfPhone ?? null);
|
|
6448
6564
|
if (result.ok) {
|
|
6449
|
-
console.error(`${
|
|
6565
|
+
console.error(`${TAG20} self-healed account=${accountId}`);
|
|
6450
6566
|
} else {
|
|
6451
|
-
console.error(`${
|
|
6567
|
+
console.error(`${TAG20} self-heal FAILED account=${accountId}: ${result.error}`);
|
|
6452
6568
|
}
|
|
6453
6569
|
}
|
|
6454
6570
|
entries.push({ accountId, selfPhone, connected: false, linkedUnconfigured: !configured });
|
|
@@ -6457,7 +6573,7 @@ function reconcileCredsOnDisk(opts) {
|
|
|
6457
6573
|
}
|
|
6458
6574
|
|
|
6459
6575
|
// server/routes/whatsapp.ts
|
|
6460
|
-
var
|
|
6576
|
+
var TAG21 = "[whatsapp:api]";
|
|
6461
6577
|
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
6462
6578
|
var app3 = new Hono();
|
|
6463
6579
|
app3.get("/status", (c) => {
|
|
@@ -6469,10 +6585,10 @@ app3.get("/status", (c) => {
|
|
|
6469
6585
|
const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds });
|
|
6470
6586
|
const accounts = [...live, ...reconciled];
|
|
6471
6587
|
const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}`).join(", ");
|
|
6472
|
-
console.error(`${
|
|
6588
|
+
console.error(`${TAG21} status accounts=${accounts.length} [${summary}]`);
|
|
6473
6589
|
return c.json({ accounts });
|
|
6474
6590
|
} catch (err) {
|
|
6475
|
-
console.error(`${
|
|
6591
|
+
console.error(`${TAG21} status error: ${String(err)}`);
|
|
6476
6592
|
return c.json({ error: String(err) }, 500);
|
|
6477
6593
|
}
|
|
6478
6594
|
});
|
|
@@ -6488,10 +6604,10 @@ app3.post("/login/start", async (c) => {
|
|
|
6488
6604
|
const authDir = join6(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
6489
6605
|
const accountDir = resolveAccount()?.accountDir ?? null;
|
|
6490
6606
|
const result = await startLogin({ accountId, authDir, phone, accountDir, force });
|
|
6491
|
-
console.error(`${
|
|
6607
|
+
console.error(`${TAG21} login/start result account=${accountId} hasCode=${!!result.pairingCode}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
6492
6608
|
return c.json(result);
|
|
6493
6609
|
} catch (err) {
|
|
6494
|
-
console.error(`${
|
|
6610
|
+
console.error(`${TAG21} login/start error: ${String(err)}`);
|
|
6495
6611
|
return c.json({ error: String(err) }, 500);
|
|
6496
6612
|
}
|
|
6497
6613
|
});
|
|
@@ -6501,7 +6617,7 @@ app3.post("/login/wait", async (c) => {
|
|
|
6501
6617
|
const accountId = validateAccountId(body.accountId);
|
|
6502
6618
|
const timeoutMs = body.timeoutMs ?? 6e4;
|
|
6503
6619
|
const result = await waitForLogin({ accountId, timeoutMs });
|
|
6504
|
-
console.error(`${
|
|
6620
|
+
console.error(`${TAG21} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
|
|
6505
6621
|
return c.json({
|
|
6506
6622
|
connected: result.connected,
|
|
6507
6623
|
message: result.message,
|
|
@@ -6509,7 +6625,7 @@ app3.post("/login/wait", async (c) => {
|
|
|
6509
6625
|
configPersisted: result.configPersisted
|
|
6510
6626
|
});
|
|
6511
6627
|
} catch (err) {
|
|
6512
|
-
console.error(`${
|
|
6628
|
+
console.error(`${TAG21} login/wait error: ${String(err)}`);
|
|
6513
6629
|
return c.json({ error: String(err) }, 500);
|
|
6514
6630
|
}
|
|
6515
6631
|
});
|
|
@@ -6520,7 +6636,7 @@ app3.post("/disconnect", async (c) => {
|
|
|
6520
6636
|
await stopConnection(accountId);
|
|
6521
6637
|
return c.json({ disconnected: true, accountId });
|
|
6522
6638
|
} catch (err) {
|
|
6523
|
-
console.error(`${
|
|
6639
|
+
console.error(`${TAG21} disconnect error: ${String(err)}`);
|
|
6524
6640
|
return c.json({ error: String(err) }, 500);
|
|
6525
6641
|
}
|
|
6526
6642
|
});
|
|
@@ -6531,7 +6647,7 @@ app3.post("/reconnect", async (c) => {
|
|
|
6531
6647
|
await startConnection(accountId);
|
|
6532
6648
|
return c.json({ reconnecting: true, accountId });
|
|
6533
6649
|
} catch (err) {
|
|
6534
|
-
console.error(`${
|
|
6650
|
+
console.error(`${TAG21} reconnect error: ${String(err)}`);
|
|
6535
6651
|
return c.json({ error: String(err) }, 500);
|
|
6536
6652
|
}
|
|
6537
6653
|
});
|
|
@@ -6552,7 +6668,7 @@ app3.post("/config", async (c) => {
|
|
|
6552
6668
|
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
6553
6669
|
}
|
|
6554
6670
|
const result = addAdminPhone(account.accountDir, phone);
|
|
6555
|
-
console.error(`${
|
|
6671
|
+
console.error(`${TAG21} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
|
|
6556
6672
|
return c.json(result, result.ok ? 200 : 400);
|
|
6557
6673
|
}
|
|
6558
6674
|
case "remove-admin-phone": {
|
|
@@ -6560,12 +6676,12 @@ app3.post("/config", async (c) => {
|
|
|
6560
6676
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
6561
6677
|
}
|
|
6562
6678
|
const result = removeAdminPhone(account.accountDir, phone);
|
|
6563
|
-
console.error(`${
|
|
6679
|
+
console.error(`${TAG21} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
|
|
6564
6680
|
return c.json(result, result.ok ? 200 : 400);
|
|
6565
6681
|
}
|
|
6566
6682
|
case "list-admin-phones": {
|
|
6567
6683
|
const phones = readAdminPhones(account.accountDir);
|
|
6568
|
-
console.error(`${
|
|
6684
|
+
console.error(`${TAG21} config action=list-admin-phones count=${phones.length}`);
|
|
6569
6685
|
return c.json({ ok: true, phones });
|
|
6570
6686
|
}
|
|
6571
6687
|
case "set-public-agent": {
|
|
@@ -6573,13 +6689,13 @@ app3.post("/config", async (c) => {
|
|
|
6573
6689
|
return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
|
|
6574
6690
|
}
|
|
6575
6691
|
const result = setPublicAgent(account.accountDir, slug);
|
|
6576
|
-
console.error(`${
|
|
6692
|
+
console.error(`${TAG21} config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
6577
6693
|
return c.json(result, result.ok ? 200 : 400);
|
|
6578
6694
|
}
|
|
6579
6695
|
case "get-public-agent": {
|
|
6580
6696
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
6581
6697
|
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount });
|
|
6582
|
-
console.error(`${
|
|
6698
|
+
console.error(`${TAG21} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
6583
6699
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
6584
6700
|
}
|
|
6585
6701
|
case "list-public-agents": {
|
|
@@ -6596,26 +6712,26 @@ app3.post("/config", async (c) => {
|
|
|
6596
6712
|
const config = JSON.parse(readFileSync7(configPath2, "utf-8"));
|
|
6597
6713
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
6598
6714
|
} catch {
|
|
6599
|
-
console.error(`${
|
|
6715
|
+
console.error(`${TAG21} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
6600
6716
|
}
|
|
6601
6717
|
}
|
|
6602
6718
|
} catch (err) {
|
|
6603
|
-
console.error(`${
|
|
6719
|
+
console.error(`${TAG21} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
|
|
6604
6720
|
}
|
|
6605
6721
|
}
|
|
6606
|
-
console.error(`${
|
|
6722
|
+
console.error(`${TAG21} config action=list-public-agents count=${agents.length}`);
|
|
6607
6723
|
return c.json({ ok: true, agents });
|
|
6608
6724
|
}
|
|
6609
6725
|
case "schema": {
|
|
6610
6726
|
const text = serializeWhatsAppSchema();
|
|
6611
|
-
console.error(`${
|
|
6727
|
+
console.error(`${TAG21} config action=schema`);
|
|
6612
6728
|
return c.json({ ok: true, text });
|
|
6613
6729
|
}
|
|
6614
6730
|
case "list-groups": {
|
|
6615
6731
|
const groupAccountId = accountId ?? "default";
|
|
6616
6732
|
const sock = getSocket(groupAccountId);
|
|
6617
6733
|
if (!sock) {
|
|
6618
|
-
console.error(`${
|
|
6734
|
+
console.error(`${TAG21} config action=list-groups error="not connected" accountId=${groupAccountId}`);
|
|
6619
6735
|
return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
|
|
6620
6736
|
}
|
|
6621
6737
|
try {
|
|
@@ -6625,10 +6741,10 @@ app3.post("/config", async (c) => {
|
|
|
6625
6741
|
name: g.subject ?? g.id,
|
|
6626
6742
|
participantCount: Array.isArray(g.participants) ? g.participants.length : 0
|
|
6627
6743
|
}));
|
|
6628
|
-
console.error(`${
|
|
6744
|
+
console.error(`${TAG21} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
|
|
6629
6745
|
return c.json({ ok: true, groups });
|
|
6630
6746
|
} catch (err) {
|
|
6631
|
-
console.error(`${
|
|
6747
|
+
console.error(`${TAG21} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
|
|
6632
6748
|
return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
|
|
6633
6749
|
}
|
|
6634
6750
|
}
|
|
@@ -6638,12 +6754,12 @@ app3.post("/config", async (c) => {
|
|
|
6638
6754
|
}
|
|
6639
6755
|
const result = updateConfig(account.accountDir, fields);
|
|
6640
6756
|
const fieldNames = Object.keys(fields);
|
|
6641
|
-
console.error(`${
|
|
6757
|
+
console.error(`${TAG21} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
|
|
6642
6758
|
return c.json(result, result.ok ? 200 : 400);
|
|
6643
6759
|
}
|
|
6644
6760
|
case "get-config": {
|
|
6645
6761
|
const waConfig = getConfig(account.accountDir);
|
|
6646
|
-
console.error(`${
|
|
6762
|
+
console.error(`${TAG21} config action=get-config`);
|
|
6647
6763
|
return c.json({ ok: true, config: waConfig });
|
|
6648
6764
|
}
|
|
6649
6765
|
default:
|
|
@@ -6653,7 +6769,7 @@ app3.post("/config", async (c) => {
|
|
|
6653
6769
|
);
|
|
6654
6770
|
}
|
|
6655
6771
|
} catch (err) {
|
|
6656
|
-
console.error(`${
|
|
6772
|
+
console.error(`${TAG21} config error: ${String(err)}`);
|
|
6657
6773
|
return c.json({ ok: false, error: String(err) }, 500);
|
|
6658
6774
|
}
|
|
6659
6775
|
});
|
|
@@ -6674,7 +6790,7 @@ app3.post("/send-document", async (c) => {
|
|
|
6674
6790
|
recordRouteDocumentOutbound(to, filePath);
|
|
6675
6791
|
return c.json({ success: true, messageId: result.messageId });
|
|
6676
6792
|
} catch (err) {
|
|
6677
|
-
console.error(`${
|
|
6793
|
+
console.error(`${TAG21} send-document error: ${String(err)}`);
|
|
6678
6794
|
return c.json({ error: String(err) }, 500);
|
|
6679
6795
|
}
|
|
6680
6796
|
});
|
|
@@ -6684,11 +6800,11 @@ app3.get("/activity", (c) => {
|
|
|
6684
6800
|
const result = getChannelActivity(accountId);
|
|
6685
6801
|
const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
|
|
6686
6802
|
console.error(
|
|
6687
|
-
`${
|
|
6803
|
+
`${TAG21} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
|
|
6688
6804
|
);
|
|
6689
6805
|
return c.json(result);
|
|
6690
6806
|
} catch (err) {
|
|
6691
|
-
console.error(`${
|
|
6807
|
+
console.error(`${TAG21} activity error: ${String(err)}`);
|
|
6692
6808
|
return c.json({ error: String(err) }, 500);
|
|
6693
6809
|
}
|
|
6694
6810
|
});
|
|
@@ -6707,10 +6823,10 @@ app3.get("/conversations", (c) => {
|
|
|
6707
6823
|
};
|
|
6708
6824
|
});
|
|
6709
6825
|
conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
|
|
6710
|
-
console.error(`${
|
|
6826
|
+
console.error(`${TAG21} conversations account=${accountId} count=${conversations.length}`);
|
|
6711
6827
|
return c.json({ conversations });
|
|
6712
6828
|
} catch (err) {
|
|
6713
|
-
console.error(`${
|
|
6829
|
+
console.error(`${TAG21} conversations error: ${String(err)}`);
|
|
6714
6830
|
return c.json({ error: String(err) }, 500);
|
|
6715
6831
|
}
|
|
6716
6832
|
});
|
|
@@ -6725,10 +6841,10 @@ app3.get("/messages", (c) => {
|
|
|
6725
6841
|
const limit = limitParam ? parseInt(limitParam, 10) : void 0;
|
|
6726
6842
|
const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
|
|
6727
6843
|
const messages = getMessages(accountId, jid, effectiveLimit);
|
|
6728
|
-
console.error(`${
|
|
6844
|
+
console.error(`${TAG21} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
|
|
6729
6845
|
return c.json({ messages });
|
|
6730
6846
|
} catch (err) {
|
|
6731
|
-
console.error(`${
|
|
6847
|
+
console.error(`${TAG21} messages error: ${String(err)}`);
|
|
6732
6848
|
return c.json({ error: String(err) }, 500);
|
|
6733
6849
|
}
|
|
6734
6850
|
});
|
|
@@ -6809,7 +6925,7 @@ app3.get("/conversation-graph-state", async (c) => {
|
|
|
6809
6925
|
}
|
|
6810
6926
|
} catch (err) {
|
|
6811
6927
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6812
|
-
console.error(`${
|
|
6928
|
+
console.error(`${TAG21} conversation-graph-state ERR cacheKey=${cacheKey} reason=${msg}`);
|
|
6813
6929
|
return c.json({ error: `Graph query failed: ${msg}`, cacheKey, cypher: cypher.trim() }, 500);
|
|
6814
6930
|
}
|
|
6815
6931
|
const ms = Date.now() - t0;
|
|
@@ -6822,7 +6938,7 @@ app3.get("/conversation-graph-state", async (c) => {
|
|
|
6822
6938
|
ms
|
|
6823
6939
|
});
|
|
6824
6940
|
} catch (err) {
|
|
6825
|
-
console.error(`${
|
|
6941
|
+
console.error(`${TAG21} conversation-graph-state error: ${String(err)}`);
|
|
6826
6942
|
return c.json({ error: String(err) }, 500);
|
|
6827
6943
|
}
|
|
6828
6944
|
});
|
|
@@ -6834,12 +6950,12 @@ app3.get("/group-info", async (c) => {
|
|
|
6834
6950
|
return c.json({ error: "Missing required parameter: jid" }, 400);
|
|
6835
6951
|
}
|
|
6836
6952
|
if (!isGroupJid(jid)) {
|
|
6837
|
-
console.error(`${
|
|
6953
|
+
console.error(`${TAG21} group-info error="not a group JID" jid=${jid} account=${accountId}`);
|
|
6838
6954
|
return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
|
|
6839
6955
|
}
|
|
6840
6956
|
const sock = getSocket(accountId);
|
|
6841
6957
|
if (!sock) {
|
|
6842
|
-
console.error(`${
|
|
6958
|
+
console.error(`${TAG21} group-info error="not connected" account=${accountId}`);
|
|
6843
6959
|
return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
|
|
6844
6960
|
}
|
|
6845
6961
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -6852,10 +6968,10 @@ app3.get("/group-info", async (c) => {
|
|
|
6852
6968
|
participantCount: meta.participants.length,
|
|
6853
6969
|
participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
|
|
6854
6970
|
};
|
|
6855
|
-
console.error(`${
|
|
6971
|
+
console.error(`${TAG21} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
|
|
6856
6972
|
return c.json(result);
|
|
6857
6973
|
} catch (err) {
|
|
6858
|
-
console.error(`${
|
|
6974
|
+
console.error(`${TAG21} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
|
|
6859
6975
|
return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
|
|
6860
6976
|
}
|
|
6861
6977
|
});
|
|
@@ -7231,6 +7347,9 @@ function resolveOperatorDisplay(userIdRes, nameRes) {
|
|
|
7231
7347
|
}
|
|
7232
7348
|
|
|
7233
7349
|
// app/lib/whatsapp-reader/parse-transcript.ts
|
|
7350
|
+
var WA_REPLY_TOOL = "mcp__whatsapp-channel__reply";
|
|
7351
|
+
var WA_REPLY_DOCUMENT_TOOL = "mcp__whatsapp-channel__reply-document";
|
|
7352
|
+
var WEBCHAT_REPLY_TOOL = "mcp__webchat-channel__reply";
|
|
7234
7353
|
var CLI_MARKER_PREFIXES2 = ["<local-command-", "<command-name>", "<command-message>"];
|
|
7235
7354
|
var CHANNEL_WRAPPER = /^<channel\b[^>]*>\n?([\s\S]*?)\n?<\/channel>$/;
|
|
7236
7355
|
function asString(content) {
|
|
@@ -7247,6 +7366,14 @@ function assistantTurns(content, ts) {
|
|
|
7247
7366
|
const b = block;
|
|
7248
7367
|
if (b.type === "text" && typeof b.text === "string") {
|
|
7249
7368
|
out.push({ kind: "agent-text", text: b.text, ts });
|
|
7369
|
+
} else if (b.type === "tool_use" && (b.name === WA_REPLY_TOOL || b.name === WEBCHAT_REPLY_TOOL)) {
|
|
7370
|
+
const text = b.input?.text;
|
|
7371
|
+
out.push({ kind: "agent-reply", text: typeof text === "string" ? text : "", ts });
|
|
7372
|
+
} else if (b.type === "tool_use" && b.name === WA_REPLY_DOCUMENT_TOOL) {
|
|
7373
|
+
const input = b.input ?? {};
|
|
7374
|
+
const files = Array.isArray(input.files) ? input.files.filter((f) => typeof f === "string") : [];
|
|
7375
|
+
const caption = typeof input.caption === "string" ? input.caption : null;
|
|
7376
|
+
out.push({ kind: "agent-reply-document", files, caption, ts });
|
|
7250
7377
|
} else if (b.type === "tool_use" && typeof b.name === "string") {
|
|
7251
7378
|
out.push({ kind: "tool-call", name: b.name, input: b.input ?? null, ts });
|
|
7252
7379
|
}
|
|
@@ -7291,6 +7418,16 @@ function parseTranscript(lines) {
|
|
|
7291
7418
|
out.push(...assistantTurns(msg?.content, ts));
|
|
7292
7419
|
continue;
|
|
7293
7420
|
}
|
|
7421
|
+
if (row.type === "attachment") {
|
|
7422
|
+
const att = row.attachment;
|
|
7423
|
+
const isChannelQueued = att?.type === "queued_command" && att.isMeta === true && typeof att.origin === "object" && att.origin !== null && att.origin.kind === "channel" && typeof att.prompt === "string";
|
|
7424
|
+
if (isChannelQueued) {
|
|
7425
|
+
const prompt = att.prompt;
|
|
7426
|
+
const m = prompt.match(CHANNEL_WRAPPER);
|
|
7427
|
+
out.push({ kind: "operator-inbound", text: m ? m[1] : prompt, ts });
|
|
7428
|
+
}
|
|
7429
|
+
continue;
|
|
7430
|
+
}
|
|
7294
7431
|
if (row.type !== "user" || !msg) continue;
|
|
7295
7432
|
if (Array.isArray(msg.content)) {
|
|
7296
7433
|
out.push(...toolResultTurns(msg.content, ts));
|
|
@@ -7565,9 +7702,57 @@ app5.get("/directive", requireAdminSession, (c) => {
|
|
|
7565
7702
|
});
|
|
7566
7703
|
var whatsapp_reader_default = app5;
|
|
7567
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
|
+
|
|
7568
7753
|
// server/routes/telegram.ts
|
|
7569
7754
|
import { homedir } from "os";
|
|
7570
|
-
import { resolve as resolve10, join as
|
|
7755
|
+
import { resolve as resolve10, join as join11 } from "path";
|
|
7571
7756
|
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
7572
7757
|
|
|
7573
7758
|
// app/lib/telegram/access-control.ts
|
|
@@ -7615,11 +7800,11 @@ function routeTelegramUpdate(input) {
|
|
|
7615
7800
|
}
|
|
7616
7801
|
|
|
7617
7802
|
// server/routes/telegram.ts
|
|
7618
|
-
var
|
|
7803
|
+
var TAG22 = "[telegram-inbound]";
|
|
7619
7804
|
var DISPATCH_TIMEOUT_MS = 12e4;
|
|
7620
7805
|
function configDirName() {
|
|
7621
7806
|
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve10(process.cwd(), "..");
|
|
7622
|
-
const brandPath =
|
|
7807
|
+
const brandPath = join11(platformRoot2, "config", "brand.json");
|
|
7623
7808
|
if (existsSync8(brandPath)) {
|
|
7624
7809
|
try {
|
|
7625
7810
|
return JSON.parse(readFileSync11(brandPath, "utf-8")).configDir ?? ".maxy";
|
|
@@ -7650,18 +7835,18 @@ app6.post("/", async (c) => {
|
|
|
7650
7835
|
const botParam = c.req.query("bot");
|
|
7651
7836
|
const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
|
|
7652
7837
|
if (!botType) {
|
|
7653
|
-
console.error(`${
|
|
7838
|
+
console.error(`${TAG22} op=reject reason=missing-bot-param`);
|
|
7654
7839
|
return c.json({ ok: false }, 400);
|
|
7655
7840
|
}
|
|
7656
7841
|
const sp = secretPath(botType);
|
|
7657
7842
|
if (!existsSync8(sp)) {
|
|
7658
|
-
console.error(`${
|
|
7843
|
+
console.error(`${TAG22} op=reject reason=no-secret-file botType=${botType}`);
|
|
7659
7844
|
return c.json({ ok: false }, 401);
|
|
7660
7845
|
}
|
|
7661
7846
|
const expected = readFileSync11(sp, "utf-8").trim();
|
|
7662
7847
|
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
7663
7848
|
if (got !== expected) {
|
|
7664
|
-
console.error(`${
|
|
7849
|
+
console.error(`${TAG22} op=reject reason=bad-secret botType=${botType}`);
|
|
7665
7850
|
return c.json({ ok: false }, 401);
|
|
7666
7851
|
}
|
|
7667
7852
|
let update;
|
|
@@ -7672,25 +7857,25 @@ app6.post("/", async (c) => {
|
|
|
7672
7857
|
}
|
|
7673
7858
|
const account = resolveAccount();
|
|
7674
7859
|
if (!account) {
|
|
7675
|
-
console.error(`${
|
|
7860
|
+
console.error(`${TAG22} op=reject reason=no-account`);
|
|
7676
7861
|
return c.json({ ok: true }, 200);
|
|
7677
7862
|
}
|
|
7678
7863
|
const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
|
|
7679
7864
|
if (decision.kind === "ignore") {
|
|
7680
|
-
console.error(`${
|
|
7865
|
+
console.error(`${TAG22} op=ignore reason=${decision.reason}`);
|
|
7681
7866
|
return c.json({ ok: true }, 200);
|
|
7682
7867
|
}
|
|
7683
7868
|
if (decision.kind === "denied") {
|
|
7684
|
-
console.error(`${
|
|
7869
|
+
console.error(`${TAG22} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
|
|
7685
7870
|
return c.json({ ok: true }, 200);
|
|
7686
7871
|
}
|
|
7687
7872
|
const role = decision.agentType === "admin" ? "admin" : "public";
|
|
7688
7873
|
const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
|
|
7689
7874
|
if (!agentSlug) {
|
|
7690
|
-
console.error(`${
|
|
7875
|
+
console.error(`${TAG22} op=reject reason=no-default-agent`);
|
|
7691
7876
|
return c.json({ ok: true }, 200);
|
|
7692
7877
|
}
|
|
7693
|
-
console.error(`${
|
|
7878
|
+
console.error(`${TAG22} op=update accountId=${account.accountId} from=${decision.senderId}`);
|
|
7694
7879
|
const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
|
|
7695
7880
|
const { senderId, chatId, text } = decision;
|
|
7696
7881
|
void (async () => {
|
|
@@ -7704,16 +7889,16 @@ app6.post("/", async (c) => {
|
|
|
7704
7889
|
timeoutMs: DISPATCH_TIMEOUT_MS
|
|
7705
7890
|
});
|
|
7706
7891
|
if ("error" in result) {
|
|
7707
|
-
console.error(`${
|
|
7892
|
+
console.error(`${TAG22} op=dispatch-failed from=${senderId} error=${result.error}`);
|
|
7708
7893
|
return;
|
|
7709
7894
|
}
|
|
7710
7895
|
if (!botToken) {
|
|
7711
7896
|
const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
|
|
7712
|
-
console.error(`${
|
|
7897
|
+
console.error(`${TAG22} op=reply-dropped reason=${reason} botType=${botType}`);
|
|
7713
7898
|
return;
|
|
7714
7899
|
}
|
|
7715
7900
|
const sent = await sendTelegram(botToken, chatId, result.turnText);
|
|
7716
|
-
console.error(`${
|
|
7901
|
+
console.error(`${TAG22} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
|
|
7717
7902
|
})();
|
|
7718
7903
|
return c.json({ ok: true }, 200);
|
|
7719
7904
|
});
|
|
@@ -7726,7 +7911,7 @@ import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
|
|
|
7726
7911
|
|
|
7727
7912
|
// ../lib/admins-write/src/index.ts
|
|
7728
7913
|
import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync5, renameSync, mkdirSync as mkdirSync2, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
7729
|
-
import { dirname as
|
|
7914
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
7730
7915
|
function logLine(input, result) {
|
|
7731
7916
|
const userIdShort = input.userId.slice(0, 8);
|
|
7732
7917
|
console.error(
|
|
@@ -7734,7 +7919,7 @@ function logLine(input, result) {
|
|
|
7734
7919
|
);
|
|
7735
7920
|
}
|
|
7736
7921
|
function writeFileAtomic(filePath, contents, mode) {
|
|
7737
|
-
mkdirSync2(
|
|
7922
|
+
mkdirSync2(dirname3(filePath), { recursive: true });
|
|
7738
7923
|
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
7739
7924
|
writeFileSync5(tempPath, contents, mode !== void 0 ? { mode } : void 0);
|
|
7740
7925
|
renameSync(tempPath, filePath);
|
|
@@ -7768,7 +7953,7 @@ function writeAdminEntry(input) {
|
|
|
7768
7953
|
return result;
|
|
7769
7954
|
}
|
|
7770
7955
|
try {
|
|
7771
|
-
const accountJsonPath =
|
|
7956
|
+
const accountJsonPath = join12(input.accountDir, "account.json");
|
|
7772
7957
|
if (!existsSync9(accountJsonPath)) {
|
|
7773
7958
|
throw new Error(`account.json not found at ${accountJsonPath}`);
|
|
7774
7959
|
}
|
|
@@ -7823,13 +8008,13 @@ function checkAdminAuthInvariant(input) {
|
|
|
7823
8008
|
}
|
|
7824
8009
|
for (const entry of entries) {
|
|
7825
8010
|
if (entry.startsWith(".")) continue;
|
|
7826
|
-
const accountDir =
|
|
8011
|
+
const accountDir = join12(input.accountsDir, entry);
|
|
7827
8012
|
try {
|
|
7828
8013
|
if (!statSync5(accountDir).isDirectory()) continue;
|
|
7829
8014
|
} catch {
|
|
7830
8015
|
continue;
|
|
7831
8016
|
}
|
|
7832
|
-
const accountJsonPath =
|
|
8017
|
+
const accountJsonPath = join12(accountDir, "account.json");
|
|
7833
8018
|
if (!existsSync9(accountJsonPath)) continue;
|
|
7834
8019
|
let admins = [];
|
|
7835
8020
|
try {
|
|
@@ -7920,11 +8105,17 @@ app7.post("/claude-auth", async (c) => {
|
|
|
7920
8105
|
return c.json({ stopped: true });
|
|
7921
8106
|
}
|
|
7922
8107
|
if (body.action === "logout") {
|
|
8108
|
+
const start = Date.now();
|
|
8109
|
+
let logoutError = null;
|
|
7923
8110
|
try {
|
|
7924
8111
|
execFileSync2("claude", ["auth", "logout"], { encoding: "utf-8", timeout: 8e3 });
|
|
7925
|
-
} catch {
|
|
8112
|
+
} catch (err) {
|
|
8113
|
+
logoutError = err instanceof Error ? err.message : String(err);
|
|
7926
8114
|
}
|
|
7927
|
-
|
|
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 });
|
|
7928
8119
|
}
|
|
7929
8120
|
if (body.action === "wait") {
|
|
7930
8121
|
const authenticated = await checkAuthStatus();
|
|
@@ -8112,8 +8303,8 @@ var onboarding_default = app7;
|
|
|
8112
8303
|
|
|
8113
8304
|
// server/routes/client-error.ts
|
|
8114
8305
|
import { appendFileSync, existsSync as existsSync11, renameSync as renameSync2, statSync as statSync6 } from "fs";
|
|
8115
|
-
import { join as
|
|
8116
|
-
var CLIENT_ERRORS_LOG =
|
|
8306
|
+
import { join as join13 } from "path";
|
|
8307
|
+
var CLIENT_ERRORS_LOG = join13(LOG_DIR, "client-errors.log");
|
|
8117
8308
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8118
8309
|
var MAX_BODY_SIZE = 8 * 1024;
|
|
8119
8310
|
var MAX_STACK_LEN = 2e3;
|
|
@@ -8439,16 +8630,16 @@ var session_default = app9;
|
|
|
8439
8630
|
|
|
8440
8631
|
// server/routes/admin/logs.ts
|
|
8441
8632
|
import { existsSync as existsSync13, readdirSync as readdirSync8, readFileSync as readFileSync14, statSync as statSync7 } from "fs";
|
|
8442
|
-
import { resolve as resolve11, basename as
|
|
8633
|
+
import { resolve as resolve11, basename as basename4 } from "path";
|
|
8443
8634
|
|
|
8444
8635
|
// app/lib/logs-read-resolve.ts
|
|
8445
8636
|
import { existsSync as existsSync12 } from "fs";
|
|
8446
|
-
import { join as
|
|
8637
|
+
import { join as join14 } from "path";
|
|
8447
8638
|
function resolveSessionLogPaths(filename, logDirs) {
|
|
8448
8639
|
const tried = [filename];
|
|
8449
8640
|
const hits = [];
|
|
8450
8641
|
for (const dir of logDirs) {
|
|
8451
|
-
const fullPath =
|
|
8642
|
+
const fullPath = join14(dir, filename);
|
|
8452
8643
|
if (existsSync12(fullPath)) {
|
|
8453
8644
|
hits.push({ path: fullPath, dir });
|
|
8454
8645
|
}
|
|
@@ -8471,7 +8662,7 @@ app10.get("/", async (c) => {
|
|
|
8471
8662
|
if (accountLogDir) logDirs.push(accountLogDir);
|
|
8472
8663
|
logDirs.push(LOG_DIR);
|
|
8473
8664
|
if (fileParam) {
|
|
8474
|
-
const safe =
|
|
8665
|
+
const safe = basename4(fileParam);
|
|
8475
8666
|
const searched = [];
|
|
8476
8667
|
for (const dir of logDirs) {
|
|
8477
8668
|
const filePath = resolve11(dir, safe);
|
|
@@ -8541,7 +8732,7 @@ app10.get("/", async (c) => {
|
|
|
8541
8732
|
if (hit) {
|
|
8542
8733
|
console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
|
|
8543
8734
|
try {
|
|
8544
|
-
const filename =
|
|
8735
|
+
const filename = basename4(hit.path);
|
|
8545
8736
|
const buffer = readFileSync14(hit.path);
|
|
8546
8737
|
const onDiskBytes = statSync7(hit.path).size;
|
|
8547
8738
|
const headers = {
|
|
@@ -9450,7 +9641,7 @@ var sessions_default = app14;
|
|
|
9450
9641
|
|
|
9451
9642
|
// app/lib/claude-agent/spawn-context.ts
|
|
9452
9643
|
import { existsSync as existsSync17, readFileSync as readFileSync16, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
|
|
9453
|
-
import { dirname as
|
|
9644
|
+
import { dirname as dirname4, resolve as resolve14, join as join15 } from "path";
|
|
9454
9645
|
async function resolveOwnerProfileBlock(accountId, userId) {
|
|
9455
9646
|
if (!userId) return { ok: false, reason: "missing-user-id" };
|
|
9456
9647
|
try {
|
|
@@ -9479,8 +9670,8 @@ function frontmatterField(manifest, field) {
|
|
|
9479
9670
|
function extractPluginToolDescriptions(pluginDir) {
|
|
9480
9671
|
const out = /* @__PURE__ */ new Map();
|
|
9481
9672
|
const candidates = [
|
|
9482
|
-
|
|
9483
|
-
|
|
9673
|
+
join15(pluginDir, "mcp/dist/index.js"),
|
|
9674
|
+
join15(pluginDir, "mcp/src/index.ts")
|
|
9484
9675
|
];
|
|
9485
9676
|
let raw = null;
|
|
9486
9677
|
for (const path2 of candidates) {
|
|
@@ -9538,21 +9729,21 @@ function listPluginDirs() {
|
|
|
9538
9729
|
const platformPlugins = resolve14(PLATFORM_ROOT, "plugins");
|
|
9539
9730
|
if (existsSync17(platformPlugins)) {
|
|
9540
9731
|
for (const name of readdirSync10(platformPlugins)) {
|
|
9541
|
-
const dir =
|
|
9732
|
+
const dir = join15(platformPlugins, name);
|
|
9542
9733
|
try {
|
|
9543
9734
|
if (statSync8(dir).isDirectory()) out.set(name, dir);
|
|
9544
9735
|
} catch {
|
|
9545
9736
|
}
|
|
9546
9737
|
}
|
|
9547
9738
|
}
|
|
9548
|
-
const premiumRoot = resolve14(
|
|
9739
|
+
const premiumRoot = resolve14(dirname4(PLATFORM_ROOT), "premium-plugins");
|
|
9549
9740
|
if (existsSync17(premiumRoot)) {
|
|
9550
9741
|
for (const bundle of readdirSync10(premiumRoot)) {
|
|
9551
|
-
const bundlePlugins =
|
|
9742
|
+
const bundlePlugins = join15(premiumRoot, bundle, "plugins");
|
|
9552
9743
|
if (!existsSync17(bundlePlugins)) continue;
|
|
9553
9744
|
try {
|
|
9554
9745
|
for (const name of readdirSync10(bundlePlugins)) {
|
|
9555
|
-
const dir =
|
|
9746
|
+
const dir = join15(bundlePlugins, name);
|
|
9556
9747
|
try {
|
|
9557
9748
|
if (statSync8(dir).isDirectory()) out.set(name, dir);
|
|
9558
9749
|
} catch {
|
|
@@ -9595,7 +9786,7 @@ function computeActivePlugins(accountId) {
|
|
|
9595
9786
|
for (const name of Array.from(enabled).sort()) {
|
|
9596
9787
|
const dir = pluginDirs.get(name);
|
|
9597
9788
|
if (!dir) continue;
|
|
9598
|
-
const fm = readFrontmatter(
|
|
9789
|
+
const fm = readFrontmatter(join15(dir, "PLUGIN.md"));
|
|
9599
9790
|
if (!fm) continue;
|
|
9600
9791
|
const description = frontmatterField(`---
|
|
9601
9792
|
${fm}
|
|
@@ -9612,7 +9803,7 @@ ${fm}
|
|
|
9612
9803
|
`plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
|
|
9613
9804
|
);
|
|
9614
9805
|
if (toolNames.length > 0 && descMap.size === 0) {
|
|
9615
|
-
const hasSource = existsSync17(
|
|
9806
|
+
const hasSource = existsSync17(join15(dir, "mcp/dist/index.js")) || existsSync17(join15(dir, "mcp/src/index.ts"));
|
|
9616
9807
|
if (hasSource) {
|
|
9617
9808
|
console.warn(
|
|
9618
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`
|
|
@@ -9622,7 +9813,7 @@ ${fm}
|
|
|
9622
9813
|
const skillPaths = parseSkillPathsFromFrontmatter(fm);
|
|
9623
9814
|
const skills = [];
|
|
9624
9815
|
for (const relPath of skillPaths) {
|
|
9625
|
-
const skillPath =
|
|
9816
|
+
const skillPath = join15(dir, relPath);
|
|
9626
9817
|
const skillFm = readFrontmatter(skillPath);
|
|
9627
9818
|
if (!skillFm) continue;
|
|
9628
9819
|
const skillName = frontmatterField(`---
|
|
@@ -9666,7 +9857,7 @@ function computeSpecialistDomains(accountId) {
|
|
|
9666
9857
|
const out = [];
|
|
9667
9858
|
for (const file of entries.sort()) {
|
|
9668
9859
|
if (!file.endsWith(".md")) continue;
|
|
9669
|
-
const fm = readFrontmatter(
|
|
9860
|
+
const fm = readFrontmatter(join15(specialistsDir, file));
|
|
9670
9861
|
if (!fm) continue;
|
|
9671
9862
|
const wrapped = `---
|
|
9672
9863
|
${fm}
|
|
@@ -9759,7 +9950,7 @@ function resolveTunnelUrl() {
|
|
|
9759
9950
|
if (!state) return null;
|
|
9760
9951
|
return `https://${hostname2}.${state.domain}`;
|
|
9761
9952
|
}
|
|
9762
|
-
var
|
|
9953
|
+
var TAG23 = "[claude-session-manager:wrapper]";
|
|
9763
9954
|
async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
9764
9955
|
const auth = await ensureAuth();
|
|
9765
9956
|
if (auth.status !== "dead" && auth.status !== "missing") return null;
|
|
@@ -9777,12 +9968,12 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
9777
9968
|
const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
|
|
9778
9969
|
const ownerMs = Date.now() - ownerStart;
|
|
9779
9970
|
const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
|
|
9780
|
-
console.log(`${
|
|
9971
|
+
console.log(`${TAG23} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
|
|
9781
9972
|
const dormantPlugins = computeDormantPlugins(args.senderId);
|
|
9782
9973
|
const activePlugins = computeActivePlugins(args.senderId);
|
|
9783
9974
|
const specialistDomains = computeSpecialistDomains(args.senderId);
|
|
9784
9975
|
const tunnelUrl = resolveTunnelUrl();
|
|
9785
|
-
console.log(`${
|
|
9976
|
+
console.log(`${TAG23} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
9786
9977
|
const upstreamPayload = JSON.stringify({
|
|
9787
9978
|
senderId: args.senderId,
|
|
9788
9979
|
// Task 205 — pass userId through to the manager so it lands as
|
|
@@ -9809,24 +10000,24 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
9809
10000
|
// unshapely values.
|
|
9810
10001
|
conversationNodeId: args.conversationNodeId
|
|
9811
10002
|
});
|
|
9812
|
-
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"}`);
|
|
9813
10004
|
const forwardStart = Date.now();
|
|
9814
10005
|
const upstream = await fetch(`${managerBase3("claude-session-manager:wrapper")}/public-spawn`, {
|
|
9815
10006
|
method: "POST",
|
|
9816
10007
|
headers: { "content-type": "application/json" },
|
|
9817
10008
|
body: upstreamPayload
|
|
9818
10009
|
}).catch((err) => {
|
|
9819
|
-
console.error(`${
|
|
10010
|
+
console.error(`${TAG23} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
|
|
9820
10011
|
return null;
|
|
9821
10012
|
});
|
|
9822
10013
|
if (!upstream) return {
|
|
9823
10014
|
response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
|
|
9824
10015
|
claudeSessionId: null
|
|
9825
10016
|
};
|
|
9826
|
-
console.log(`${
|
|
10017
|
+
console.log(`${TAG23} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
|
|
9827
10018
|
if (args.initialMessage) {
|
|
9828
10019
|
const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
|
|
9829
|
-
console.log(`${
|
|
10020
|
+
console.log(`${TAG23} initial-message-inlined bytes=${inputBytes}`);
|
|
9830
10021
|
}
|
|
9831
10022
|
const bodyText = await upstream.text().catch(() => "");
|
|
9832
10023
|
let claudeSessionId = null;
|
|
@@ -9861,7 +10052,7 @@ app15.post("/", async (c) => {
|
|
|
9861
10052
|
if (refusal) return refusal;
|
|
9862
10053
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
9863
10054
|
if (!senderId) {
|
|
9864
|
-
console.error(`${
|
|
10055
|
+
console.error(`${TAG23} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
|
|
9865
10056
|
return c.json({ error: "admin-account-not-resolved" }, 500);
|
|
9866
10057
|
}
|
|
9867
10058
|
const userId = getUserIdForSession(cacheKey) ?? void 0;
|
|
@@ -9870,7 +10061,7 @@ app15.post("/", async (c) => {
|
|
|
9870
10061
|
const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
|
|
9871
10062
|
const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
|
|
9872
10063
|
const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
|
|
9873
|
-
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"}`);
|
|
9874
10065
|
const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
|
|
9875
10066
|
const { response, claudeSessionId } = await performSpawnWithInitialMessage({
|
|
9876
10067
|
senderId,
|
|
@@ -9889,13 +10080,13 @@ app15.post("/", async (c) => {
|
|
|
9889
10080
|
claudeSessionId,
|
|
9890
10081
|
senderId
|
|
9891
10082
|
);
|
|
9892
|
-
console.log(`${
|
|
10083
|
+
console.log(`${TAG23} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
|
|
9893
10084
|
return response;
|
|
9894
10085
|
});
|
|
9895
10086
|
var claude_sessions_default = app15;
|
|
9896
10087
|
|
|
9897
10088
|
// server/routes/admin/log-ingest.ts
|
|
9898
|
-
var
|
|
10089
|
+
var TAG24 = "[log-ingest]";
|
|
9899
10090
|
var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
|
|
9900
10091
|
var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
9901
10092
|
var MAX_LINE_BYTES = 4096;
|
|
@@ -9906,7 +10097,7 @@ function isLoopbackAddr(addr) {
|
|
|
9906
10097
|
app16.post("/", async (c) => {
|
|
9907
10098
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
9908
10099
|
if (!isLoopbackAddr(remoteAddr)) {
|
|
9909
|
-
console.error(`${
|
|
10100
|
+
console.error(`${TAG24} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
9910
10101
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
9911
10102
|
}
|
|
9912
10103
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -9915,7 +10106,7 @@ app16.post("/", async (c) => {
|
|
|
9915
10106
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
9916
10107
|
const offender = tokens.find((t) => !isLoopbackAddr(t));
|
|
9917
10108
|
if (offender !== void 0) {
|
|
9918
|
-
console.error(`${
|
|
10109
|
+
console.error(`${TAG24} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
9919
10110
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
9920
10111
|
}
|
|
9921
10112
|
}
|
|
@@ -9957,18 +10148,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
9957
10148
|
]);
|
|
9958
10149
|
var app17 = new Hono();
|
|
9959
10150
|
app17.post("/", async (c) => {
|
|
9960
|
-
const
|
|
10151
|
+
const TAG32 = "[admin:events]";
|
|
9961
10152
|
let body;
|
|
9962
10153
|
try {
|
|
9963
10154
|
body = await c.req.json();
|
|
9964
10155
|
} catch (err) {
|
|
9965
10156
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9966
|
-
console.error(`${
|
|
10157
|
+
console.error(`${TAG32} reject reason=body-not-json detail=${detail}`);
|
|
9967
10158
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
9968
10159
|
}
|
|
9969
10160
|
const event = typeof body.event === "string" ? body.event : "";
|
|
9970
10161
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
9971
|
-
console.error(`${
|
|
10162
|
+
console.error(`${TAG32} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
9972
10163
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
9973
10164
|
}
|
|
9974
10165
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -9993,7 +10184,7 @@ var events_default = app17;
|
|
|
9993
10184
|
import { createReadStream as createReadStream2 } from "fs";
|
|
9994
10185
|
import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
9995
10186
|
import { realpathSync as realpathSync4 } from "fs";
|
|
9996
|
-
import { basename as
|
|
10187
|
+
import { basename as basename6, dirname as dirname5, join as join17, resolve as resolve18, sep as sep5 } from "path";
|
|
9997
10188
|
import { Readable as Readable2 } from "stream";
|
|
9998
10189
|
|
|
9999
10190
|
// app/lib/data-path.ts
|
|
@@ -10373,7 +10564,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
10373
10564
|
|
|
10374
10565
|
// app/lib/file-index.ts
|
|
10375
10566
|
import * as fsp from "fs/promises";
|
|
10376
|
-
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";
|
|
10377
10568
|
import { tmpdir as tmpdir2 } from "os";
|
|
10378
10569
|
import { execFile as execFile2 } from "child_process";
|
|
10379
10570
|
import { promisify as promisify2 } from "util";
|
|
@@ -10444,7 +10635,7 @@ async function extractPdf(absolute) {
|
|
|
10444
10635
|
return stdout.trim();
|
|
10445
10636
|
}
|
|
10446
10637
|
async function ocrPdf(absolute) {
|
|
10447
|
-
const outPdf =
|
|
10638
|
+
const outPdf = join16(tmpdir2(), `file-index-ocr-${process.pid}-${Date.now()}.pdf`);
|
|
10448
10639
|
try {
|
|
10449
10640
|
await execFileAsync2(
|
|
10450
10641
|
"ocrmypdf",
|
|
@@ -10500,13 +10691,13 @@ async function extractFileContent(absolute) {
|
|
|
10500
10691
|
}
|
|
10501
10692
|
}
|
|
10502
10693
|
async function readDisplayName(absolute) {
|
|
10503
|
-
const dir = absolute.slice(0, absolute.length -
|
|
10504
|
-
const base =
|
|
10694
|
+
const dir = absolute.slice(0, absolute.length - basename5(absolute).length);
|
|
10695
|
+
const base = basename5(absolute);
|
|
10505
10696
|
const dot = base.lastIndexOf(".");
|
|
10506
10697
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
10507
10698
|
if (base === `${stem}.meta.json`) return null;
|
|
10508
10699
|
try {
|
|
10509
|
-
const raw = await fsp.readFile(
|
|
10700
|
+
const raw = await fsp.readFile(join16(dir, `${stem}.meta.json`), "utf-8");
|
|
10510
10701
|
const meta = JSON.parse(raw);
|
|
10511
10702
|
const dn = meta.displayName ?? meta.filename;
|
|
10512
10703
|
return typeof dn === "string" && dn.length > 0 ? dn : null;
|
|
@@ -10526,7 +10717,7 @@ async function walkSubtree(root, dataRoot, out) {
|
|
|
10526
10717
|
let clean = true;
|
|
10527
10718
|
for (const entry of entries) {
|
|
10528
10719
|
if (entry.isSymbolicLink()) continue;
|
|
10529
|
-
const abs =
|
|
10720
|
+
const abs = join16(root, entry.name);
|
|
10530
10721
|
if (entry.isDirectory()) {
|
|
10531
10722
|
const sub = await walkSubtree(abs, dataRoot, out);
|
|
10532
10723
|
if (!sub.clean) clean = false;
|
|
@@ -10638,7 +10829,7 @@ async function cascadeTrashLinkedKnowledgeDocs(session, accountId, relativePaths
|
|
|
10638
10829
|
return { kds, sections, chunks };
|
|
10639
10830
|
}
|
|
10640
10831
|
async function buildArtifact(accountId, wf, embed2) {
|
|
10641
|
-
const name =
|
|
10832
|
+
const name = basename5(wf.absolute);
|
|
10642
10833
|
const { content, route } = await extractFileContent(wf.absolute);
|
|
10643
10834
|
const displayName = await readDisplayName(wf.absolute);
|
|
10644
10835
|
const embedInput = `${name}
|
|
@@ -10797,7 +10988,7 @@ async function dropFileIndex(accountId, relativePath, opts) {
|
|
|
10797
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;
|
|
10798
10989
|
async function readMeta(absDir, baseName) {
|
|
10799
10990
|
try {
|
|
10800
|
-
const raw = await readFile4(
|
|
10991
|
+
const raw = await readFile4(join17(absDir, `${baseName}.meta.json`), "utf8");
|
|
10801
10992
|
const parsed = JSON.parse(raw);
|
|
10802
10993
|
if (typeof parsed?.filename === "string") {
|
|
10803
10994
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -10835,7 +11026,7 @@ async function readAccountNames() {
|
|
|
10835
11026
|
}
|
|
10836
11027
|
async function enrich(absolute, entry, accountNames) {
|
|
10837
11028
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
10838
|
-
const meta = await readMeta(
|
|
11029
|
+
const meta = await readMeta(join17(absolute, entry.name), entry.name);
|
|
10839
11030
|
if (meta?.filename) {
|
|
10840
11031
|
entry.displayName = meta.filename;
|
|
10841
11032
|
entry.mimeType = meta.mimeType;
|
|
@@ -10933,7 +11124,7 @@ app18.get("/", requireAdminSession, async (c) => {
|
|
|
10933
11124
|
continue;
|
|
10934
11125
|
}
|
|
10935
11126
|
try {
|
|
10936
|
-
const entryPath =
|
|
11127
|
+
const entryPath = join17(absolute, name);
|
|
10937
11128
|
const s = await stat4(entryPath);
|
|
10938
11129
|
entries.push({
|
|
10939
11130
|
name,
|
|
@@ -10999,7 +11190,7 @@ app18.get("/download", requireAdminSession, async (c) => {
|
|
|
10999
11190
|
if (!info.isFile()) {
|
|
11000
11191
|
return c.json({ error: "Path is not a file" }, 400);
|
|
11001
11192
|
}
|
|
11002
|
-
const filename =
|
|
11193
|
+
const filename = basename6(absolute);
|
|
11003
11194
|
const mimeType = detectMimeType(absolute);
|
|
11004
11195
|
const nodeStream = createReadStream2(absolute);
|
|
11005
11196
|
const webStream = Readable2.toWeb(nodeStream);
|
|
@@ -11056,7 +11247,7 @@ app18.post("/upload", requireAdminSession, async (c) => {
|
|
|
11056
11247
|
error: `Unsupported file type: "${file.type}". Supported: ${[...SUPPORTED_MIME_TYPES].join(", ")}.`
|
|
11057
11248
|
}, 422);
|
|
11058
11249
|
}
|
|
11059
|
-
const safeName =
|
|
11250
|
+
const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
|
|
11060
11251
|
const finalName = `${Date.now()}-${safeName}`;
|
|
11061
11252
|
const destDir = resolve18(DATA_ROOT, "uploads", accountId);
|
|
11062
11253
|
const destPath = resolve18(destDir, finalName);
|
|
@@ -11108,7 +11299,7 @@ app18.delete("/", requireAdminSession, async (c) => {
|
|
|
11108
11299
|
console.error(`[data] account-scope-blocked endpoint="DELETE /api/admin/files" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
|
|
11109
11300
|
return c.json({ error: "Not found" }, 404);
|
|
11110
11301
|
}
|
|
11111
|
-
const base =
|
|
11302
|
+
const base = basename6(absolute);
|
|
11112
11303
|
const segments = relPath.split("/").filter(Boolean);
|
|
11113
11304
|
if (base === "account.json" || segments.includes(".git")) {
|
|
11114
11305
|
console.error(`[data] file-delete blocked path="${relPath}" reason="protected"`);
|
|
@@ -11124,7 +11315,7 @@ app18.delete("/", requireAdminSession, async (c) => {
|
|
|
11124
11315
|
}
|
|
11125
11316
|
const dot = base.lastIndexOf(".");
|
|
11126
11317
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
11127
|
-
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;
|
|
11128
11319
|
await unlink2(absolute);
|
|
11129
11320
|
if (sidecarPath) {
|
|
11130
11321
|
try {
|
|
@@ -12100,6 +12291,12 @@ var FILTER_TOP_LEVEL_LABELS = Object.freeze(
|
|
|
12100
12291
|
"Property",
|
|
12101
12292
|
"Viewing",
|
|
12102
12293
|
"Offer",
|
|
12294
|
+
"Job",
|
|
12295
|
+
"QuoteDocument",
|
|
12296
|
+
"InboundInvoice",
|
|
12297
|
+
"SubContractor",
|
|
12298
|
+
"SubInvoice",
|
|
12299
|
+
"WhatsAppGroup",
|
|
12103
12300
|
"AdminConversation",
|
|
12104
12301
|
"PublicConversation",
|
|
12105
12302
|
"Task",
|
|
@@ -13167,7 +13364,7 @@ var graph_default_view_default = app24;
|
|
|
13167
13364
|
|
|
13168
13365
|
// server/routes/admin/sidebar-artefacts.ts
|
|
13169
13366
|
import { readdir as readdir4, stat as stat5 } from "fs/promises";
|
|
13170
|
-
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";
|
|
13171
13368
|
import { existsSync as existsSync19 } from "fs";
|
|
13172
13369
|
var LIMIT = 50;
|
|
13173
13370
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
@@ -13219,7 +13416,7 @@ async function fetchAccountFileArtefacts(accountId) {
|
|
|
13219
13416
|
const modifiedAt = r.get("modifiedAt") ?? "";
|
|
13220
13417
|
return {
|
|
13221
13418
|
id: `account-file:${relativePath}`,
|
|
13222
|
-
name: displayName ??
|
|
13419
|
+
name: displayName ?? basename7(relativePath),
|
|
13223
13420
|
kind: "account-file",
|
|
13224
13421
|
updatedAt: modifiedAt,
|
|
13225
13422
|
mimeType,
|
|
@@ -13625,10 +13822,10 @@ var system_stats_default = app30;
|
|
|
13625
13822
|
|
|
13626
13823
|
// server/routes/admin/health.ts
|
|
13627
13824
|
import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
|
|
13628
|
-
import { resolve as resolve20, join as
|
|
13825
|
+
import { resolve as resolve20, join as join18 } from "path";
|
|
13629
13826
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
|
|
13630
13827
|
var brandHostname = "maxy";
|
|
13631
|
-
var brandJsonPath =
|
|
13828
|
+
var brandJsonPath = join18(PLATFORM_ROOT6, "config", "brand.json");
|
|
13632
13829
|
if (existsSync20(brandJsonPath)) {
|
|
13633
13830
|
try {
|
|
13634
13831
|
const brand = JSON.parse(readFileSync18(brandJsonPath, "utf-8"));
|
|
@@ -13693,7 +13890,7 @@ var health_default2 = app31;
|
|
|
13693
13890
|
|
|
13694
13891
|
// server/routes/admin/linkedin-ingest.ts
|
|
13695
13892
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
13696
|
-
var
|
|
13893
|
+
var TAG25 = "[linkedin-ingest-route]";
|
|
13697
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}$/;
|
|
13698
13895
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
13699
13896
|
var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
|
|
@@ -13797,29 +13994,29 @@ app32.post("/", requireAdminSession, async (c) => {
|
|
|
13797
13994
|
try {
|
|
13798
13995
|
body = await c.req.json();
|
|
13799
13996
|
} catch {
|
|
13800
|
-
console.error(
|
|
13997
|
+
console.error(TAG25 + " rejected status=400 reason=schema:body-not-json");
|
|
13801
13998
|
return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
|
|
13802
13999
|
}
|
|
13803
14000
|
const v = validate(body);
|
|
13804
14001
|
if (!v.ok) {
|
|
13805
|
-
console.error(
|
|
14002
|
+
console.error(TAG25 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
|
|
13806
14003
|
return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
|
|
13807
14004
|
}
|
|
13808
14005
|
const envelope = v.envelope;
|
|
13809
14006
|
const cacheKey = c.var.cacheKey ?? "";
|
|
13810
14007
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
13811
14008
|
if (!senderId) {
|
|
13812
|
-
console.error(
|
|
14009
|
+
console.error(TAG25 + " rejected status=500 reason=admin-account-not-resolved");
|
|
13813
14010
|
return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
|
|
13814
14011
|
}
|
|
13815
14012
|
const payloadBytes = JSON.stringify(envelope).length;
|
|
13816
14013
|
console.log(
|
|
13817
|
-
|
|
14014
|
+
TAG25 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
|
|
13818
14015
|
);
|
|
13819
14016
|
const initialMessage = buildInitialMessage(envelope);
|
|
13820
14017
|
const spawnStart = Date.now();
|
|
13821
14018
|
const sessionId = randomUUID9();
|
|
13822
|
-
console.log(
|
|
14019
|
+
console.log(TAG25 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
13823
14020
|
const spawned = await managerRcSpawn({
|
|
13824
14021
|
sessionId,
|
|
13825
14022
|
initialMessage,
|
|
@@ -13828,12 +14025,12 @@ app32.post("/", requireAdminSession, async (c) => {
|
|
|
13828
14025
|
});
|
|
13829
14026
|
if ("error" in spawned) {
|
|
13830
14027
|
console.error(
|
|
13831
|
-
|
|
14028
|
+
TAG25 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
|
|
13832
14029
|
);
|
|
13833
14030
|
return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
|
|
13834
14031
|
}
|
|
13835
14032
|
console.log(
|
|
13836
|
-
|
|
14033
|
+
TAG25 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
|
|
13837
14034
|
);
|
|
13838
14035
|
return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
|
|
13839
14036
|
});
|
|
@@ -13841,7 +14038,7 @@ var linkedin_ingest_default = app32;
|
|
|
13841
14038
|
|
|
13842
14039
|
// server/routes/admin/post-turn-context.ts
|
|
13843
14040
|
import neo4j2 from "neo4j-driver";
|
|
13844
|
-
var
|
|
14041
|
+
var TAG26 = "[post-turn-context]";
|
|
13845
14042
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
13846
14043
|
"embedding",
|
|
13847
14044
|
"passwordHash",
|
|
@@ -13883,7 +14080,7 @@ var app33 = new Hono();
|
|
|
13883
14080
|
app33.get("/", async (c) => {
|
|
13884
14081
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
13885
14082
|
if (!isLoopbackAddr2(remoteAddr)) {
|
|
13886
|
-
console.error(`${
|
|
14083
|
+
console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
13887
14084
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
13888
14085
|
}
|
|
13889
14086
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -13892,7 +14089,7 @@ app33.get("/", async (c) => {
|
|
|
13892
14089
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
13893
14090
|
const offender = tokens.find((t) => !isLoopbackAddr2(t));
|
|
13894
14091
|
if (offender !== void 0) {
|
|
13895
|
-
console.error(`${
|
|
14092
|
+
console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
13896
14093
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
13897
14094
|
}
|
|
13898
14095
|
}
|
|
@@ -13924,7 +14121,7 @@ app33.get("/", async (c) => {
|
|
|
13924
14121
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
13925
14122
|
const total = Date.now() - started;
|
|
13926
14123
|
console.log(
|
|
13927
|
-
`${
|
|
14124
|
+
`${TAG26} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
|
|
13928
14125
|
);
|
|
13929
14126
|
return c.json({
|
|
13930
14127
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -13933,7 +14130,7 @@ app33.get("/", async (c) => {
|
|
|
13933
14130
|
const elapsed = Date.now() - started;
|
|
13934
14131
|
const message = err instanceof Error ? err.message : String(err);
|
|
13935
14132
|
console.error(
|
|
13936
|
-
`${
|
|
14133
|
+
`${TAG26} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
|
|
13937
14134
|
);
|
|
13938
14135
|
return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
|
|
13939
14136
|
} finally {
|
|
@@ -13944,7 +14141,7 @@ var post_turn_context_default = app33;
|
|
|
13944
14141
|
|
|
13945
14142
|
// server/routes/admin/public-session-context.ts
|
|
13946
14143
|
import neo4j3 from "neo4j-driver";
|
|
13947
|
-
var
|
|
14144
|
+
var TAG27 = "[public-session-context]";
|
|
13948
14145
|
var STRIPPED_PROPERTIES3 = /* @__PURE__ */ new Set([
|
|
13949
14146
|
"embedding",
|
|
13950
14147
|
"passwordHash",
|
|
@@ -13986,7 +14183,7 @@ var app34 = new Hono();
|
|
|
13986
14183
|
app34.get("/", async (c) => {
|
|
13987
14184
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
13988
14185
|
if (!isLoopbackAddr3(remoteAddr)) {
|
|
13989
|
-
console.error(`${
|
|
14186
|
+
console.error(`${TAG27} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
13990
14187
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
13991
14188
|
}
|
|
13992
14189
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -13995,7 +14192,7 @@ app34.get("/", async (c) => {
|
|
|
13995
14192
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
13996
14193
|
const offender = tokens.find((t) => !isLoopbackAddr3(t));
|
|
13997
14194
|
if (offender !== void 0) {
|
|
13998
|
-
console.error(`${
|
|
14195
|
+
console.error(`${TAG27} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
13999
14196
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
14000
14197
|
}
|
|
14001
14198
|
}
|
|
@@ -14026,7 +14223,7 @@ app34.get("/", async (c) => {
|
|
|
14026
14223
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
14027
14224
|
const total = Date.now() - started;
|
|
14028
14225
|
console.log(
|
|
14029
|
-
`${
|
|
14226
|
+
`${TAG27} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
|
|
14030
14227
|
);
|
|
14031
14228
|
return c.json({
|
|
14032
14229
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -14035,7 +14232,7 @@ app34.get("/", async (c) => {
|
|
|
14035
14232
|
const elapsed = Date.now() - started;
|
|
14036
14233
|
const message = err instanceof Error ? err.message : String(err);
|
|
14037
14234
|
console.error(
|
|
14038
|
-
`${
|
|
14235
|
+
`${TAG27} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
|
|
14039
14236
|
);
|
|
14040
14237
|
return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
|
|
14041
14238
|
} finally {
|
|
@@ -14045,7 +14242,7 @@ app34.get("/", async (c) => {
|
|
|
14045
14242
|
var public_session_context_default = app34;
|
|
14046
14243
|
|
|
14047
14244
|
// server/routes/admin/public-session-exit.ts
|
|
14048
|
-
var
|
|
14245
|
+
var TAG28 = "[public-session-exit-route]";
|
|
14049
14246
|
function isLoopbackAddr4(addr) {
|
|
14050
14247
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
14051
14248
|
}
|
|
@@ -14053,7 +14250,7 @@ var app35 = new Hono();
|
|
|
14053
14250
|
app35.post("/", async (c) => {
|
|
14054
14251
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
14055
14252
|
if (!isLoopbackAddr4(remoteAddr)) {
|
|
14056
|
-
console.error(`${
|
|
14253
|
+
console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
14057
14254
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
14058
14255
|
}
|
|
14059
14256
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -14062,7 +14259,7 @@ app35.post("/", async (c) => {
|
|
|
14062
14259
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
14063
14260
|
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
14064
14261
|
if (offender !== void 0) {
|
|
14065
|
-
console.error(`${
|
|
14262
|
+
console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
14066
14263
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
14067
14264
|
}
|
|
14068
14265
|
}
|
|
@@ -14080,7 +14277,7 @@ app35.post("/", async (c) => {
|
|
|
14080
14277
|
var public_session_exit_default = app35;
|
|
14081
14278
|
|
|
14082
14279
|
// server/routes/admin/access-session-evict.ts
|
|
14083
|
-
var
|
|
14280
|
+
var TAG29 = "[access-session-evict]";
|
|
14084
14281
|
function isLoopbackAddr5(addr) {
|
|
14085
14282
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
14086
14283
|
}
|
|
@@ -14088,7 +14285,7 @@ var app36 = new Hono();
|
|
|
14088
14285
|
app36.post("/", async (c) => {
|
|
14089
14286
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
14090
14287
|
if (!isLoopbackAddr5(remoteAddr)) {
|
|
14091
|
-
console.error(`${
|
|
14288
|
+
console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
14092
14289
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
14093
14290
|
}
|
|
14094
14291
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -14097,7 +14294,7 @@ app36.post("/", async (c) => {
|
|
|
14097
14294
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
14098
14295
|
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
14099
14296
|
if (offender !== void 0) {
|
|
14100
|
-
console.error(`${
|
|
14297
|
+
console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
14101
14298
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
14102
14299
|
}
|
|
14103
14300
|
}
|
|
@@ -14110,7 +14307,7 @@ app36.post("/", async (c) => {
|
|
|
14110
14307
|
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
14111
14308
|
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
14112
14309
|
const dropped = evictAccessSessionsByGrant(grantId);
|
|
14113
|
-
console.log(`${
|
|
14310
|
+
console.log(`${TAG29} grantId=${grantId} dropped=${dropped}`);
|
|
14114
14311
|
return c.json({ ok: true, dropped });
|
|
14115
14312
|
});
|
|
14116
14313
|
var access_session_evict_default = app36;
|
|
@@ -14390,7 +14587,7 @@ async function generateNewMagicToken(grantId) {
|
|
|
14390
14587
|
}
|
|
14391
14588
|
|
|
14392
14589
|
// server/routes/access/verify-token.ts
|
|
14393
|
-
var
|
|
14590
|
+
var TAG30 = "[access-verify]";
|
|
14394
14591
|
var MINT_TAG = "[access-session-mint]";
|
|
14395
14592
|
var COOKIE_NAME = "__access_session";
|
|
14396
14593
|
var app39 = new Hono();
|
|
@@ -14409,39 +14606,39 @@ app39.post("/", async (c) => {
|
|
|
14409
14606
|
}
|
|
14410
14607
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
14411
14608
|
if (rateMsg) {
|
|
14412
|
-
console.error(`${
|
|
14609
|
+
console.error(`${TAG30} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
14413
14610
|
return c.json({ error: rateMsg }, 429);
|
|
14414
14611
|
}
|
|
14415
14612
|
const grant = await findGrantByMagicToken(token);
|
|
14416
14613
|
if (!grant) {
|
|
14417
14614
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14418
|
-
console.error(`${
|
|
14615
|
+
console.error(`${TAG30} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
14419
14616
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
14420
14617
|
}
|
|
14421
14618
|
if (grant.agentSlug !== agentSlug) {
|
|
14422
14619
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14423
14620
|
console.error(
|
|
14424
|
-
`${
|
|
14621
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
14425
14622
|
);
|
|
14426
14623
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
14427
14624
|
}
|
|
14428
14625
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
14429
14626
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14430
14627
|
console.error(
|
|
14431
|
-
`${
|
|
14628
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
14432
14629
|
);
|
|
14433
14630
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
14434
14631
|
}
|
|
14435
14632
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
14436
14633
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14437
14634
|
console.error(
|
|
14438
|
-
`${
|
|
14635
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
14439
14636
|
);
|
|
14440
14637
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
14441
14638
|
}
|
|
14442
14639
|
if (!grant.sliceToken) {
|
|
14443
14640
|
console.error(
|
|
14444
|
-
`${
|
|
14641
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
14445
14642
|
);
|
|
14446
14643
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
14447
14644
|
}
|
|
@@ -14457,12 +14654,12 @@ app39.post("/", async (c) => {
|
|
|
14457
14654
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
14458
14655
|
} catch (err) {
|
|
14459
14656
|
console.error(
|
|
14460
|
-
`${
|
|
14657
|
+
`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
14461
14658
|
);
|
|
14462
14659
|
return c.json({ error: "verification-failed" }, 500);
|
|
14463
14660
|
}
|
|
14464
14661
|
clearAccessRateLimit(ip, agentSlug);
|
|
14465
|
-
console.log(`${
|
|
14662
|
+
console.log(`${TAG30} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
14466
14663
|
console.log(
|
|
14467
14664
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
14468
14665
|
);
|
|
@@ -14538,7 +14735,7 @@ async function sendMagicLinkEmail(payload) {
|
|
|
14538
14735
|
}
|
|
14539
14736
|
|
|
14540
14737
|
// server/routes/access/request-magic-link.ts
|
|
14541
|
-
var
|
|
14738
|
+
var TAG31 = "[access-request-link]";
|
|
14542
14739
|
var app40 = new Hono();
|
|
14543
14740
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
14544
14741
|
app40.post("/", async (c) => {
|
|
@@ -14555,18 +14752,18 @@ app40.post("/", async (c) => {
|
|
|
14555
14752
|
}
|
|
14556
14753
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
14557
14754
|
if (rateMsg) {
|
|
14558
|
-
console.error(`${
|
|
14755
|
+
console.error(`${TAG31} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
14559
14756
|
return c.json({ error: rateMsg }, 429);
|
|
14560
14757
|
}
|
|
14561
14758
|
recordRequestLinkAttempt(contactValue);
|
|
14562
14759
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
14563
14760
|
if (!accountId) {
|
|
14564
|
-
console.error(`${
|
|
14761
|
+
console.error(`${TAG31} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
14565
14762
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14566
14763
|
}
|
|
14567
14764
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
14568
14765
|
if (!grant) {
|
|
14569
|
-
console.log(`${
|
|
14766
|
+
console.log(`${TAG31} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
14570
14767
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14571
14768
|
}
|
|
14572
14769
|
let token;
|
|
@@ -14574,7 +14771,7 @@ app40.post("/", async (c) => {
|
|
|
14574
14771
|
token = await generateNewMagicToken(grant.grantId);
|
|
14575
14772
|
} catch (err) {
|
|
14576
14773
|
console.error(
|
|
14577
|
-
`${
|
|
14774
|
+
`${TAG31} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
14578
14775
|
);
|
|
14579
14776
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14580
14777
|
}
|
|
@@ -14606,12 +14803,12 @@ app40.post("/", async (c) => {
|
|
|
14606
14803
|
});
|
|
14607
14804
|
if (!sendResult.ok) {
|
|
14608
14805
|
console.error(
|
|
14609
|
-
`${
|
|
14806
|
+
`${TAG31} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
14610
14807
|
);
|
|
14611
14808
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14612
14809
|
}
|
|
14613
14810
|
console.log(
|
|
14614
|
-
`${
|
|
14811
|
+
`${TAG31} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
14615
14812
|
);
|
|
14616
14813
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14617
14814
|
});
|
|
@@ -14765,7 +14962,7 @@ var sites_default = app42;
|
|
|
14765
14962
|
// app/lib/visitor-token.ts
|
|
14766
14963
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
14767
14964
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync21, writeFileSync as writeFileSync7 } from "fs";
|
|
14768
|
-
import { dirname as
|
|
14965
|
+
import { dirname as dirname6 } from "path";
|
|
14769
14966
|
var TOKEN_PREFIX = "v1.";
|
|
14770
14967
|
var SECRET_BYTES = 32;
|
|
14771
14968
|
var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -14782,7 +14979,7 @@ function getSecret() {
|
|
|
14782
14979
|
}
|
|
14783
14980
|
const fresh = randomBytes(SECRET_BYTES).toString("hex");
|
|
14784
14981
|
try {
|
|
14785
|
-
mkdirSync4(
|
|
14982
|
+
mkdirSync4(dirname6(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
|
|
14786
14983
|
writeFileSync7(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
|
|
14787
14984
|
console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
|
|
14788
14985
|
} catch {
|
|
@@ -14841,7 +15038,7 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
|
|
|
14841
15038
|
|
|
14842
15039
|
// app/lib/brand-config.ts
|
|
14843
15040
|
import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
|
|
14844
|
-
import { join as
|
|
15041
|
+
import { join as join19 } from "path";
|
|
14845
15042
|
var cached2 = null;
|
|
14846
15043
|
var cachedAttempted = false;
|
|
14847
15044
|
function readBrandConfig() {
|
|
@@ -14849,7 +15046,7 @@ function readBrandConfig() {
|
|
|
14849
15046
|
cachedAttempted = true;
|
|
14850
15047
|
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
|
|
14851
15048
|
if (!platformRoot2) return null;
|
|
14852
|
-
const brandPath =
|
|
15049
|
+
const brandPath = join19(platformRoot2, "config", "brand.json");
|
|
14853
15050
|
if (!existsSync22(brandPath)) return null;
|
|
14854
15051
|
try {
|
|
14855
15052
|
cached2 = JSON.parse(readFileSync22(brandPath, "utf-8"));
|
|
@@ -16295,7 +16492,264 @@ var WaGateway = class {
|
|
|
16295
16492
|
}
|
|
16296
16493
|
};
|
|
16297
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
|
+
|
|
16298
16751
|
// app/lib/whatsapp/gateway/native-file-follower.ts
|
|
16752
|
+
var COMPOSING_REFRESH_MS = 1e4;
|
|
16299
16753
|
function startNativeFileFollower(input) {
|
|
16300
16754
|
const entry = {
|
|
16301
16755
|
sessionId: input.sessionId,
|
|
@@ -16314,6 +16768,24 @@ function startNativeFileFollower(input) {
|
|
|
16314
16768
|
personId: null,
|
|
16315
16769
|
name: null
|
|
16316
16770
|
};
|
|
16771
|
+
const sid = input.senderId.slice(0, 8);
|
|
16772
|
+
let lastComposingAt = Number.NEGATIVE_INFINITY;
|
|
16773
|
+
const onActivity = () => {
|
|
16774
|
+
const now = Date.now();
|
|
16775
|
+
if (now - lastComposingAt < COMPOSING_REFRESH_MS) return;
|
|
16776
|
+
lastComposingAt = now;
|
|
16777
|
+
const sock = getSocket(input.accountId);
|
|
16778
|
+
if (!sock) return;
|
|
16779
|
+
console.error(`[whatsapp-adaptor] op=presence state=composing sender=${sid}`);
|
|
16780
|
+
void sendComposing(sock, input.senderId);
|
|
16781
|
+
};
|
|
16782
|
+
const onTurnComplete = () => {
|
|
16783
|
+
lastComposingAt = Number.NEGATIVE_INFINITY;
|
|
16784
|
+
const sock = getSocket(input.accountId);
|
|
16785
|
+
if (!sock) return;
|
|
16786
|
+
console.error(`[whatsapp-adaptor] op=presence state=paused sender=${sid}`);
|
|
16787
|
+
void sendPaused(sock, input.senderId);
|
|
16788
|
+
};
|
|
16317
16789
|
return startFollower({
|
|
16318
16790
|
entry,
|
|
16319
16791
|
tag: "[whatsapp-adaptor]",
|
|
@@ -16321,6 +16793,8 @@ function startNativeFileFollower(input) {
|
|
|
16321
16793
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
16322
16794
|
// suppress replay so historical files are not re-sent on attach.
|
|
16323
16795
|
suppressResumeReplay: true,
|
|
16796
|
+
onActivity,
|
|
16797
|
+
onTurnComplete,
|
|
16324
16798
|
onClose: input.onClose ?? (() => {
|
|
16325
16799
|
})
|
|
16326
16800
|
});
|
|
@@ -16630,7 +17104,7 @@ function clientFrom(c) {
|
|
|
16630
17104
|
);
|
|
16631
17105
|
}
|
|
16632
17106
|
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
16633
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT9 ?
|
|
17107
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join20(PLATFORM_ROOT9, "config", "brand.json") : "";
|
|
16634
17108
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
16635
17109
|
if (BRAND_JSON_PATH && !existsSync25(BRAND_JSON_PATH)) {
|
|
16636
17110
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
@@ -16656,7 +17130,7 @@ var brandLoginOpts = {
|
|
|
16656
17130
|
bodyFont: BRAND.defaultFonts?.body,
|
|
16657
17131
|
logoContainsName: !!BRAND.logoContainsName
|
|
16658
17132
|
};
|
|
16659
|
-
var ALIAS_DOMAINS_PATH =
|
|
17133
|
+
var ALIAS_DOMAINS_PATH = join20(homedir3(), BRAND.configDir, "alias-domains.json");
|
|
16660
17134
|
function loadAliasDomains() {
|
|
16661
17135
|
try {
|
|
16662
17136
|
if (!existsSync25(ALIAS_DOMAINS_PATH)) return null;
|
|
@@ -16688,7 +17162,7 @@ var app47 = new Hono();
|
|
|
16688
17162
|
var nativeFileFollowers = /* @__PURE__ */ new Map();
|
|
16689
17163
|
var waGateway = new WaGateway({
|
|
16690
17164
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
16691
|
-
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"),
|
|
16692
17166
|
// Task 751 — file delivery on the native channel: resolve the platform
|
|
16693
17167
|
// account + path validation here and funnel through the shared send core.
|
|
16694
17168
|
sendDocument: async ({ senderId, accountId, filePath, caption }) => {
|
|
@@ -16704,7 +17178,7 @@ var waGateway = new WaGateway({
|
|
|
16704
17178
|
caption,
|
|
16705
17179
|
accountId,
|
|
16706
17180
|
maxyAccountId,
|
|
16707
|
-
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ??
|
|
17181
|
+
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join20(__dirname, ".."))
|
|
16708
17182
|
});
|
|
16709
17183
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
16710
17184
|
},
|
|
@@ -16742,6 +17216,29 @@ var waGateway = new WaGateway({
|
|
|
16742
17216
|
});
|
|
16743
17217
|
app47.route("/", waGateway.routes());
|
|
16744
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();
|
|
16745
17242
|
app47.use("*", clientIpMiddleware);
|
|
16746
17243
|
app47.use("*", async (c, next) => {
|
|
16747
17244
|
await next();
|
|
@@ -17028,6 +17525,7 @@ app47.route("/api/health", health_default);
|
|
|
17028
17525
|
app47.route("/api/chat", chat_default);
|
|
17029
17526
|
app47.route("/api/whatsapp", whatsapp_default);
|
|
17030
17527
|
app47.route("/api/whatsapp-reader", whatsapp_reader_default);
|
|
17528
|
+
app47.route("/api/webchat", createWebchatRoutes({ handleInbound: (input) => webchatGateway.handleInbound(input) }));
|
|
17031
17529
|
app47.route("/api/telegram", telegram_default);
|
|
17032
17530
|
app47.route("/api/onboarding", onboarding_default);
|
|
17033
17531
|
app47.route("/api/admin", admin_default);
|
|
@@ -17135,7 +17633,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
17135
17633
|
function readInstalledVersion() {
|
|
17136
17634
|
try {
|
|
17137
17635
|
if (!PLATFORM_ROOT9) return "unknown";
|
|
17138
|
-
const versionFile =
|
|
17636
|
+
const versionFile = join20(PLATFORM_ROOT9, "config", `.${BRAND.hostname}-version`);
|
|
17139
17637
|
if (!existsSync25(versionFile)) return "unknown";
|
|
17140
17638
|
const content = readFileSync24(versionFile, "utf-8").trim();
|
|
17141
17639
|
return content || "unknown";
|
|
@@ -17194,11 +17692,11 @@ ${clientErrorReporterScript}
|
|
|
17194
17692
|
}
|
|
17195
17693
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
17196
17694
|
function loadBrandingCache(agentSlug) {
|
|
17197
|
-
const configDir2 =
|
|
17695
|
+
const configDir2 = join20(homedir3(), BRAND.configDir);
|
|
17198
17696
|
try {
|
|
17199
17697
|
const accountId = getDefaultAccountId();
|
|
17200
17698
|
if (!accountId) return null;
|
|
17201
|
-
const cachePath =
|
|
17699
|
+
const cachePath = join20(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
17202
17700
|
if (!existsSync25(cachePath)) return null;
|
|
17203
17701
|
return JSON.parse(readFileSync24(cachePath, "utf-8"));
|
|
17204
17702
|
} catch {
|
|
@@ -17327,6 +17825,11 @@ app47.get("/graph", (c) => {
|
|
|
17327
17825
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
17328
17826
|
return c.html(cachedHtml("graph.html"));
|
|
17329
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
|
+
});
|
|
17330
17833
|
app47.get("/sessions", (c) => {
|
|
17331
17834
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
17332
17835
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
@@ -17496,7 +17999,7 @@ try {
|
|
|
17496
17999
|
} catch (err) {
|
|
17497
18000
|
console.error(`[graph-health] account-enumeration unavailable reason=${err instanceof Error ? err.message : String(err)}`);
|
|
17498
18001
|
}
|
|
17499
|
-
var configDirForWhatsApp =
|
|
18002
|
+
var configDirForWhatsApp = basename8(MAXY_DIR) || ".maxy";
|
|
17500
18003
|
var bootAccount = resolveAccount();
|
|
17501
18004
|
if (bootAccount) {
|
|
17502
18005
|
migrateRemovedConfigKeys(bootAccount.accountDir);
|
|
@@ -17558,7 +18061,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
17558
18061
|
}
|
|
17559
18062
|
init({
|
|
17560
18063
|
configDir: configDirForWhatsApp,
|
|
17561
|
-
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ??
|
|
18064
|
+
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join20(__dirname, "..")),
|
|
17562
18065
|
accountConfig: bootAccountConfig,
|
|
17563
18066
|
onMessage: async (msg) => {
|
|
17564
18067
|
if (msg.isOwnerMirror) {
|