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