@rubytech/create-maxy-code 0.1.306 → 0.1.307

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 (177) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/models/dist/index.d.ts +0 -1
  3. package/payload/platform/lib/models/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/models/dist/index.js +2 -5
  5. package/payload/platform/lib/models/dist/index.js.map +1 -1
  6. package/payload/platform/lib/models/src/index.ts +1 -4
  7. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +33 -12
  8. package/payload/platform/plugins/docs/references/admin-ui.md +32 -11
  9. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  10. package/payload/platform/services/claude-session-manager/dist/http-server.js +14 -0
  11. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  12. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  13. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +8 -0
  14. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  15. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +13 -0
  16. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
  17. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +18 -0
  18. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
  19. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  20. package/payload/platform/services/whatsapp-channel/dist/server.js +34 -10
  21. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  22. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +9 -0
  23. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  24. package/payload/platform/services/whatsapp-channel/dist/targets.js +13 -0
  25. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  26. package/payload/platform/templates/account.json +1 -1
  27. package/payload/server/{chunk-TUY3RB5G.js → chunk-4QFPGAUZ.js} +27 -0
  28. package/payload/server/maxy-edge.js +1 -1
  29. package/payload/server/public/assets/AdminLoginScreens-BsC-EwAn.js +1 -0
  30. package/payload/server/public/assets/AdminShell-CiKKN22G.js +1 -0
  31. package/payload/server/public/assets/Checkbox-DoPOlZTV.js +1 -0
  32. package/payload/server/public/assets/admin-DSwmdCKp.js +1 -0
  33. package/payload/server/public/assets/{arc-BrTRjBIM.js → arc-DxSm8tli.js} +1 -1
  34. package/payload/server/public/assets/architecture-YZFGNWBL-6g9jRVgc.js +1 -0
  35. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-DJ9on7sZ.js → architectureDiagram-Q4EWVU46-7JSAh0vL.js} +1 -1
  36. package/payload/server/public/assets/{audio-attachment-mime-j2VvxEtx.js → audio-attachment-mime-BA10IzqQ.js} +4 -4
  37. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CIcyQn0q.js → blockDiagram-DXYQGD6D-DLoVmHKD.js} +1 -1
  38. package/payload/server/public/assets/brand-BaRMO3mZ.js +9 -0
  39. package/payload/server/public/assets/{jsx-runtime-DZkld3Wk.css → brand-C001cin3.css} +1 -1
  40. package/payload/server/public/assets/browser-BKR1eJJb.js +1 -0
  41. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Dt5dh_GR.js → c4Diagram-AHTNJAMY-Nv1G0o9P.js} +1 -1
  42. package/payload/server/public/assets/channel-BgQLJanG.js +1 -0
  43. package/payload/server/public/assets/chat-DZXNRiy-.js +1 -0
  44. package/payload/server/public/assets/{chunk-2KRD3SAO-BUq3MqUy.js → chunk-2KRD3SAO-BpqPMhoA.js} +1 -1
  45. package/payload/server/public/assets/{chunk-336JU56O-CUzJH5Qt.js → chunk-336JU56O-DgXHlVU_.js} +2 -2
  46. package/payload/server/public/assets/chunk-426QAEUC-Dr_XVuUq.js +1 -0
  47. package/payload/server/public/assets/{chunk-4BX2VUAB-B4JUc3UA.js → chunk-4BX2VUAB-nwyZSZH8.js} +1 -1
  48. package/payload/server/public/assets/{chunk-4TB4RGXK-HRO9fIIs.js → chunk-4TB4RGXK-Tfxz9LHX.js} +1 -1
  49. package/payload/server/public/assets/{chunk-55IACEB6-MZdrLBuw.js → chunk-55IACEB6-6FMJQGXG.js} +1 -1
  50. package/payload/server/public/assets/{chunk-5FUZZQ4R-XcAN5zlM.js → chunk-5FUZZQ4R-g2FgZF3D.js} +1 -1
  51. package/payload/server/public/assets/{chunk-5PVQY5BW-PQ1GEsxo.js → chunk-5PVQY5BW-DtLwXsPH.js} +1 -1
  52. package/payload/server/public/assets/{chunk-67CJDMHE-BVvCaqGd.js → chunk-67CJDMHE-K91CP5ZU.js} +1 -1
  53. package/payload/server/public/assets/{chunk-7N4EOEYR-BlBbZdpM.js → chunk-7N4EOEYR-BGpCQhqK.js} +1 -1
  54. package/payload/server/public/assets/{chunk-AA7GKIK3-Ct-PgkGa.js → chunk-AA7GKIK3-jkDbInck.js} +1 -1
  55. package/payload/server/public/assets/{chunk-BSJP7CBP-DZRRfCSz.js → chunk-BSJP7CBP-mDosdzGs.js} +1 -1
  56. package/payload/server/public/assets/{chunk-CIAEETIT-BoGEJiXe.js → chunk-CIAEETIT-DhBxewpQ.js} +1 -1
  57. package/payload/server/public/assets/{chunk-EDXVE4YY-DiSmN31f.js → chunk-EDXVE4YY-B7c-9Emg.js} +1 -1
  58. package/payload/server/public/assets/{chunk-ENJZ2VHE-uhhokIvs.js → chunk-ENJZ2VHE-Cqhhncox.js} +1 -1
  59. package/payload/server/public/assets/{chunk-FMBD7UC4-CfCx5WI7.js → chunk-FMBD7UC4-BpDq6wj1.js} +1 -1
  60. package/payload/server/public/assets/{chunk-FOC6F5B3-Dj9E9gTu.js → chunk-FOC6F5B3-Ds02-ktu.js} +1 -1
  61. package/payload/server/public/assets/{chunk-ICPOFSXX-DcBs_FNy.js → chunk-ICPOFSXX-BTX5POFr.js} +2 -2
  62. package/payload/server/public/assets/{chunk-K5T4RW27-7-VefyJD.js → chunk-K5T4RW27-Du1PXXBU.js} +1 -1
  63. package/payload/server/public/assets/{chunk-KGLVRYIC-CqgTEVxm.js → chunk-KGLVRYIC-siasOAf8.js} +1 -1
  64. package/payload/server/public/assets/{chunk-LIHQZDEY-C0gzDXha.js → chunk-LIHQZDEY-xf1rbZtf.js} +1 -1
  65. package/payload/server/public/assets/{chunk-ORNJ4GCN-JXz23R1H.js → chunk-ORNJ4GCN-DeTLQ_LD.js} +1 -1
  66. package/payload/server/public/assets/{chunk-OYMX7WX6-C2snCNq9.js → chunk-OYMX7WX6-DQ7_oVJn.js} +1 -1
  67. package/payload/server/public/assets/chunk-QZHKN3VN-DF4o9HRJ.js +1 -0
  68. package/payload/server/public/assets/{chunk-U2HBQHQK-CXCU7M-M.js → chunk-U2HBQHQK-CXmFUKgn.js} +1 -1
  69. package/payload/server/public/assets/{chunk-X2U36JSP-CEdcSb28.js → chunk-X2U36JSP-Bj8-8Wkz.js} +1 -1
  70. package/payload/server/public/assets/{chunk-XPW4576I-Cckc75YS.js → chunk-XPW4576I-Con3TXqt.js} +1 -1
  71. package/payload/server/public/assets/{chunk-YZCP3GAM-4nN8AO7H.js → chunk-YZCP3GAM-Dx8BHGWf.js} +1 -1
  72. package/payload/server/public/assets/{chunk-ZZ45TVLE-DcoBc8-7.js → chunk-ZZ45TVLE-Dp4Q5Y-f.js} +1 -1
  73. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DAbPKU6u.js +1 -0
  74. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BKuFQLcv.js +1 -0
  75. package/payload/server/public/assets/clone-DnNHGhNe.js +1 -0
  76. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-BFFro_t9.js → cose-bilkent-S5V4N54A-pZiCv69E.js} +1 -1
  77. package/payload/server/public/assets/{dagre-CN5to_JM.js → dagre-CLqhEgbL.js} +1 -1
  78. package/payload/server/public/assets/{dagre-KV5264BT-WYrPwDg0.js → dagre-KV5264BT-BCxE8iyk.js} +1 -1
  79. package/payload/server/public/assets/data-CuVmW1WN.js +1 -0
  80. package/payload/server/public/assets/{diagram-5BDNPKRD-Dz2CiboN.js → diagram-5BDNPKRD-slUEdZzz.js} +1 -1
  81. package/payload/server/public/assets/{diagram-G4DWMVQ6-r8h3Ip9O.js → diagram-G4DWMVQ6-DPcraMfu.js} +1 -1
  82. package/payload/server/public/assets/{diagram-MMDJMWI5-CECcpGtr.js → diagram-MMDJMWI5-DR6luhGK.js} +1 -1
  83. package/payload/server/public/assets/{diagram-TYMM5635-CHOcAyIP.js → diagram-TYMM5635-Jap1B4wA.js} +1 -1
  84. package/payload/server/public/assets/{erDiagram-SMLLAGMA-YIqKdPbS.js → erDiagram-SMLLAGMA-DJKUs2hO.js} +1 -1
  85. package/payload/server/public/assets/file-download-4_hQmMwU.js +1 -0
  86. package/payload/server/public/assets/{flatten-DhYYI_lo.js → flatten-Cl1Bc3dR.js} +1 -1
  87. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-DaEvQWpj.js → flowDiagram-DWJPFMVM-C8W-ZZ9W.js} +1 -1
  88. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-x1hClEOB.js → ganttDiagram-T4ZO3ILL-DYCX4Xu2.js} +1 -1
  89. package/payload/server/public/assets/gitGraph-7Q5UKJZL-g7mvu6IY.js +1 -0
  90. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-C-sZsh6Z.js → gitGraphDiagram-UUTBAWPF-CZu_fpgS.js} +1 -1
  91. package/payload/server/public/assets/{graph-Uaa5heM5.js → graph-D29p1FJH.js} +3 -3
  92. package/payload/server/public/assets/graph-labels-DF3QVHkH.js +1 -0
  93. package/payload/server/public/assets/{graphlib-CNkAqm2w.js → graphlib-Bdp7TA4y.js} +1 -1
  94. package/payload/server/public/assets/info-OMHHGYJF-DZqJuIkB.js +1 -0
  95. package/payload/server/public/assets/infoDiagram-42DDH7IO-cGfGccHz.js +2 -0
  96. package/payload/server/public/assets/{isEmpty-DGhq_DBP.js → isEmpty-D1pGOD1J.js} +1 -1
  97. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-wHE-RwzZ.js → ishikawaDiagram-UXIWVN3A-B7KfIX6z.js} +1 -1
  98. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DQB6iThz.js → journeyDiagram-VCZTEJTY-CSDwBfhF.js} +1 -1
  99. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-Ck08vXSA.js → kanban-definition-6JOO6SKY-CWzcEKTr.js} +1 -1
  100. package/payload/server/public/assets/{line-Cdc2FBEU.js → line-BNQSlbYn.js} +1 -1
  101. package/payload/server/public/assets/{linear-DhtUm5H7.js → linear-BN6kGN93.js} +1 -1
  102. package/payload/server/public/assets/{mermaid-parser.core-CSYulPIL.js → mermaid-parser.core-CcFhkElq.js} +2 -2
  103. package/payload/server/public/assets/{mermaid.core-CP-hOmMh.js → mermaid.core-BsIeJ7Cc.js} +3 -3
  104. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-GxlkvV1Z.js → mindmap-definition-QFDTVHPH-CQVzj6D_.js} +1 -1
  105. package/payload/server/public/assets/operator-CQRaBNN7.js +1 -0
  106. package/payload/server/public/assets/{ordinal-CibZU3PM.js → ordinal-CBZeH4Yp.js} +1 -1
  107. package/payload/server/public/assets/packet-4T2RLAQJ-Hn8rlHpy.js +1 -0
  108. package/payload/server/public/assets/page-Dz2F3QtB.js +1 -0
  109. package/payload/server/public/assets/pie-ZZUOXDRM-BPGWVqBm.js +1 -0
  110. package/payload/server/public/assets/{pieDiagram-DEJITSTG-Di6kWh9c.js → pieDiagram-DEJITSTG-DnrQ9fyn.js} +1 -1
  111. package/payload/server/public/assets/{public-DBXfM02B.js → public-B7BQwWpP.js} +3 -3
  112. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-iNHC5bkO.js → quadrantDiagram-34T5L4WZ-BQWlCyWi.js} +1 -1
  113. package/payload/server/public/assets/radar-PYXPWWZC-DMdLrj3E.js +1 -0
  114. package/payload/server/public/assets/{reduce-Da0RBva0.js → reduce-PuPlFPRX.js} +1 -1
  115. package/payload/server/public/assets/{requirementDiagram-MS252O5E-BBlLREXo.js → requirementDiagram-MS252O5E-Du_vZhU1.js} +1 -1
  116. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-qyDkP-Ca.js → sankeyDiagram-XADWPNL6-Cf6Z8GPQ.js} +1 -1
  117. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-C0MJuS0m.js → sequenceDiagram-FGHM5R23-CS8GVol8.js} +1 -1
  118. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-aHTrlosa.js → stateDiagram-FHFEXIEX-Bv1NW2F1.js} +1 -1
  119. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BGUCxPmp.js +1 -0
  120. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BlRZIxEp.js → timeline-definition-GMOUNBTQ-ClGYAsr0.js} +1 -1
  121. package/payload/server/public/assets/treeView-SZITEDCU-CWxofUbI.js +1 -0
  122. package/payload/server/public/assets/treemap-W4RFUUIX-Cfgb_ZG0.js +1 -0
  123. package/payload/server/public/assets/useSelectionMode-ghXuZicl.js +5 -0
  124. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-s76adysx.js → vennDiagram-DHZGUBPP-C2wTcxQT.js} +1 -1
  125. package/payload/server/public/assets/wardley-RL74JXVD-B45olYjj.js +1 -0
  126. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-xC8t2QGW.js → wardleyDiagram-NUSXRM2D-D86-ivEx.js} +1 -1
  127. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-DRcHg-Wj.js → xychartDiagram-5P7HB3ND-Ba5WAN8P.js} +1 -1
  128. package/payload/server/public/browser.html +6 -6
  129. package/payload/server/public/chat.html +9 -10
  130. package/payload/server/public/data.html +5 -5
  131. package/payload/server/public/graph.html +8 -8
  132. package/payload/server/public/index.html +8 -9
  133. package/payload/server/public/operator.html +11 -12
  134. package/payload/server/public/public.html +7 -7
  135. package/payload/server/server.js +470 -221
  136. package/payload/server/public/assets/AdminLoginScreens-Cw882rmm.js +0 -1
  137. package/payload/server/public/assets/AdminShell-DpWl1uoY.js +0 -1
  138. package/payload/server/public/assets/Checkbox-DAWZItdK.js +0 -1
  139. package/payload/server/public/assets/Transcript-DbK2YA3K.js +0 -1
  140. package/payload/server/public/assets/admin-nQRdn79w.js +0 -1
  141. package/payload/server/public/assets/architecture-YZFGNWBL-DqQB3T6j.js +0 -1
  142. package/payload/server/public/assets/browser-CHWZDCXt.js +0 -1
  143. package/payload/server/public/assets/channel-NZeJdLVK.js +0 -1
  144. package/payload/server/public/assets/chat-BBK6ulFy.js +0 -1
  145. package/payload/server/public/assets/chunk-426QAEUC-DFKX5l0L.js +0 -1
  146. package/payload/server/public/assets/chunk-QZHKN3VN-BWCCN0AM.js +0 -1
  147. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DPlCQCX2.js +0 -1
  148. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CqkQq74h.js +0 -1
  149. package/payload/server/public/assets/clone-CSykdD1A.js +0 -1
  150. package/payload/server/public/assets/data-33mdnHv8.js +0 -1
  151. package/payload/server/public/assets/file-download-P_qlMCg5.js +0 -1
  152. package/payload/server/public/assets/gitGraph-7Q5UKJZL-V7Tx7_eY.js +0 -1
  153. package/payload/server/public/assets/graph-labels-rG63rHvl.js +0 -1
  154. package/payload/server/public/assets/info-OMHHGYJF-D3DqVPGn.js +0 -1
  155. package/payload/server/public/assets/infoDiagram-42DDH7IO-B_O-R0vN.js +0 -2
  156. package/payload/server/public/assets/jsx-runtime-sFVKdwlP.js +0 -9
  157. package/payload/server/public/assets/operator-CiDwt5I9.js +0 -1
  158. package/payload/server/public/assets/packet-4T2RLAQJ-BY7mgCTQ.js +0 -1
  159. package/payload/server/public/assets/page-BvkaS_tu.js +0 -1
  160. package/payload/server/public/assets/pie-ZZUOXDRM-B9f6jN54.js +0 -1
  161. package/payload/server/public/assets/radar-PYXPWWZC-BKS8JOUe.js +0 -1
  162. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-V2vo4BjY.js +0 -1
  163. package/payload/server/public/assets/treeView-SZITEDCU-bIKzyIdw.js +0 -1
  164. package/payload/server/public/assets/treemap-W4RFUUIX-CQycQ4H9.js +0 -1
  165. package/payload/server/public/assets/useSelectionMode-Dky-Stnf.js +0 -5
  166. package/payload/server/public/assets/wardley-RL74JXVD-COr9o-nG.js +0 -1
  167. /package/payload/server/public/assets/{_baseFor-BFYpZGkN.js → _baseFor-brRjpUOX.js} +0 -0
  168. /package/payload/server/public/assets/{array-CYkMkqnU.js → array-BGFCBI0e.js} +0 -0
  169. /package/payload/server/public/assets/{cytoscape.esm-DqlHsaog.js → cytoscape.esm-TvRJ2tGX.js} +0 -0
  170. /package/payload/server/public/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-CRZydyG6.js} +0 -0
  171. /package/payload/server/public/assets/{dist-t9p-NJH2.js → dist-Dbh7HrZX.js} +0 -0
  172. /package/payload/server/public/assets/{init-Rr1s_RiX.js → init-B8gtcn7T.js} +0 -0
  173. /package/payload/server/public/assets/{katex-pVJiI_rc.js → katex-RxgSu_dy.js} +0 -0
  174. /package/payload/server/public/assets/{path-BAQ3hXlG.js → path-DZF-JdEe.js} +0 -0
  175. /package/payload/server/public/assets/{preload-helper-B_l5jfgx.js → preload-helper-CQ36nxok.js} +0 -0
  176. /package/payload/server/public/assets/{rough.esm-nHaDi0Kw.js → rough.esm-6CnTHTkH.js} +0 -0
  177. /package/payload/server/public/assets/{src-DuPQVw-H.js → src-BoaldmnP.js} +0 -0
@@ -69,6 +69,7 @@ import {
69
69
  listAdminSessions,
70
70
  listValidAccounts,
71
71
  loadAdminUserName,
72
+ loadPersonNameByElementId,
72
73
  loadUserProfile,
73
74
  loadWhatsappSenderName,
74
75
  persistMessage,
@@ -105,7 +106,7 @@ import {
105
106
  vncLog,
106
107
  walkPremiumBundles,
107
108
  writeAdminUserAndPerson
108
- } from "./chunk-TUY3RB5G.js";
109
+ } from "./chunk-4QFPGAUZ.js";
109
110
  import {
110
111
  __commonJS,
111
112
  __toESM
@@ -1223,7 +1224,7 @@ var serveStatic = (options = { root: "" }) => {
1223
1224
 
1224
1225
  // server/index.ts
1225
1226
  import { readFileSync as readFileSync26, existsSync as existsSync27, watchFile } from "fs";
1226
- import { resolve as resolve29, join as join22, basename as basename8 } from "path";
1227
+ import { resolve as resolve30, join as join22, basename as basename8 } from "path";
1227
1228
  import { homedir as homedir3 } from "os";
1228
1229
  import { monitorEventLoopDelay } from "perf_hooks";
1229
1230
 
@@ -1871,7 +1872,7 @@ var credsSaveQueue = Promise.resolve();
1871
1872
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
1872
1873
  console.error(`${TAG2} draining credential save queue\u2026`);
1873
1874
  const timer2 = new Promise(
1874
- (resolve30) => setTimeout(() => resolve30("timeout"), timeoutMs)
1875
+ (resolve31) => setTimeout(() => resolve31("timeout"), timeoutMs)
1875
1876
  );
1876
1877
  const result = await Promise.race([
1877
1878
  credsSaveQueue.then(() => "drained"),
@@ -1999,11 +2000,11 @@ async function createWaSocket(opts) {
1999
2000
  return sock;
2000
2001
  }
2001
2002
  async function waitForConnection(sock) {
2002
- return new Promise((resolve30, reject) => {
2003
+ return new Promise((resolve31, reject) => {
2003
2004
  const handler = (update) => {
2004
2005
  if (update.connection === "open") {
2005
2006
  sock.ev.off("connection.update", handler);
2006
- resolve30();
2007
+ resolve31();
2007
2008
  }
2008
2009
  if (update.connection === "close") {
2009
2010
  sock.ev.off("connection.update", handler);
@@ -2117,14 +2118,14 @@ ${inspected}`;
2117
2118
  return inspect2(err, INSPECT_OPTS2);
2118
2119
  }
2119
2120
  function withTimeout(label, promise, timeoutMs) {
2120
- return new Promise((resolve30, reject) => {
2121
+ return new Promise((resolve31, reject) => {
2121
2122
  const timer2 = setTimeout(() => {
2122
2123
  reject(new Error(`${label} timed out after ${timeoutMs}ms`));
2123
2124
  }, timeoutMs);
2124
2125
  promise.then(
2125
2126
  (value) => {
2126
2127
  clearTimeout(timer2);
2127
- resolve30(value);
2128
+ resolve31(value);
2128
2129
  },
2129
2130
  (err) => {
2130
2131
  clearTimeout(timer2);
@@ -3008,8 +3009,8 @@ async function persistWhatsAppMessage(input) {
3008
3009
  const { givenName, familyName } = splitName(input.pushName);
3009
3010
  const prev = sessionWriteLocks.get(input.cacheKey);
3010
3011
  let release;
3011
- const mine = new Promise((resolve30) => {
3012
- release = resolve30;
3012
+ const mine = new Promise((resolve31) => {
3013
+ release = resolve31;
3013
3014
  });
3014
3015
  const chained = (prev ?? Promise.resolve()).then(() => mine);
3015
3016
  sessionWriteLocks.set(input.cacheKey, chained);
@@ -4089,11 +4090,11 @@ async function connectWithReconnect(conn) {
4089
4090
  console.error(
4090
4091
  `${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
4091
4092
  );
4092
- await new Promise((resolve30) => {
4093
- const timer2 = setTimeout(resolve30, delay);
4093
+ await new Promise((resolve31) => {
4094
+ const timer2 = setTimeout(resolve31, delay);
4094
4095
  conn.abortController.signal.addEventListener("abort", () => {
4095
4096
  clearTimeout(timer2);
4096
- resolve30();
4097
+ resolve31();
4097
4098
  }, { once: true });
4098
4099
  });
4099
4100
  }
@@ -4101,16 +4102,16 @@ async function connectWithReconnect(conn) {
4101
4102
  }
4102
4103
  }
4103
4104
  function waitForDisconnectEvent(conn) {
4104
- return new Promise((resolve30) => {
4105
+ return new Promise((resolve31) => {
4105
4106
  if (!conn.sock) {
4106
- resolve30();
4107
+ resolve31();
4107
4108
  return;
4108
4109
  }
4109
4110
  const sock = conn.sock;
4110
4111
  const handler = (update) => {
4111
4112
  if (update.connection === "close") {
4112
4113
  sock.ev.off("connection.update", handler);
4113
- resolve30();
4114
+ resolve31();
4114
4115
  }
4115
4116
  };
4116
4117
  sock.ev.on("connection.update", handler);
@@ -4370,8 +4371,8 @@ async function handleInboundMessage(conn, msg) {
4370
4371
  const conversationKey = isGroup ? remoteJid : senderPhone;
4371
4372
  const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
4372
4373
  let resolvePending;
4373
- const sttPending = new Promise((resolve30) => {
4374
- resolvePending = resolve30;
4374
+ const sttPending = new Promise((resolve31) => {
4375
+ resolvePending = resolve31;
4375
4376
  });
4376
4377
  if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
4377
4378
  try {
@@ -4765,20 +4766,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
4765
4766
 
4766
4767
  // server/routes/health.ts
4767
4768
  function checkPort(port2, timeoutMs = 500) {
4768
- return new Promise((resolve30) => {
4769
+ return new Promise((resolve31) => {
4769
4770
  const socket = createConnection2(port2, "127.0.0.1");
4770
4771
  socket.setTimeout(timeoutMs);
4771
4772
  socket.once("connect", () => {
4772
4773
  socket.destroy();
4773
- resolve30(true);
4774
+ resolve31(true);
4774
4775
  });
4775
4776
  socket.once("error", () => {
4776
4777
  socket.destroy();
4777
- resolve30(false);
4778
+ resolve31(false);
4778
4779
  });
4779
4780
  socket.once("timeout", () => {
4780
4781
  socket.destroy();
4781
- resolve30(false);
4782
+ resolve31(false);
4782
4783
  });
4783
4784
  });
4784
4785
  }
@@ -4847,10 +4848,75 @@ var health_default = app;
4847
4848
  // app/lib/attachments.ts
4848
4849
  import { randomUUID as randomUUID4 } from "crypto";
4849
4850
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
4851
+ import { realpathSync as realpathSync2 } from "fs";
4852
+ import { resolve as resolve6, extname } from "path";
4853
+
4854
+ // app/lib/data-path.ts
4850
4855
  import { realpathSync } from "fs";
4851
- import { resolve as resolve5, extname } from "path";
4856
+ import { resolve as resolve5, normalize, sep as sep2, relative } from "path";
4852
4857
  var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "../platform");
4853
- var ATTACHMENTS_ROOT = resolve5(PLATFORM_ROOT4, "..", "data/uploads");
4858
+ var DATA_ROOT = resolve5(PLATFORM_ROOT4, "..", "data");
4859
+ var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve5(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
4860
+ var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["accounts"]);
4861
+ var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4862
+ function crossesForeignAccountPartition(relPath, accountId) {
4863
+ const segs = relPath.split("/").filter(Boolean);
4864
+ if (segs.length < 2) return false;
4865
+ if (!ACCOUNT_PARTITION_DIRS.has(segs[0])) return false;
4866
+ const owner = segs[1];
4867
+ if (!ACCOUNT_UUID_RE.test(owner)) return false;
4868
+ return owner !== accountId;
4869
+ }
4870
+ function resolveUnderRoot(raw, rootAbs, rootLabel) {
4871
+ const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
4872
+ const absolute = resolve5(rootAbs, cleaned);
4873
+ let rootReal;
4874
+ try {
4875
+ rootReal = realpathSync(rootAbs);
4876
+ } catch (err) {
4877
+ return {
4878
+ ok: false,
4879
+ status: 500,
4880
+ error: `${rootLabel} not accessible: ${err instanceof Error ? err.message : String(err)}`
4881
+ };
4882
+ }
4883
+ let resolvedReal;
4884
+ try {
4885
+ resolvedReal = realpathSync(absolute);
4886
+ } catch (err) {
4887
+ const code = err.code;
4888
+ if (code === "ENOENT") {
4889
+ if (absolute !== rootReal && !absolute.startsWith(rootReal + sep2)) {
4890
+ return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: absolute };
4891
+ }
4892
+ return { ok: false, status: 404, error: "Not found" };
4893
+ }
4894
+ return {
4895
+ ok: false,
4896
+ status: 500,
4897
+ error: err instanceof Error ? err.message : String(err)
4898
+ };
4899
+ }
4900
+ if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + sep2)) {
4901
+ return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: resolvedReal };
4902
+ }
4903
+ const relPath = relative(rootReal, resolvedReal) || ".";
4904
+ return { ok: true, absolute: resolvedReal, dataRootReal: rootReal, relative: relPath };
4905
+ }
4906
+ function resolveDataPath(raw) {
4907
+ return resolveUnderRoot(raw, DATA_ROOT, "DATA_ROOT");
4908
+ }
4909
+ function resolveClaudeUploadsPath(raw) {
4910
+ if (!CLAUDE_UPLOADS_ROOT) {
4911
+ return { ok: false, status: 404, error: "Not found" };
4912
+ }
4913
+ return resolveUnderRoot(raw, CLAUDE_UPLOADS_ROOT, "CLAUDE_UPLOADS_ROOT");
4914
+ }
4915
+
4916
+ // app/lib/attachments.ts
4917
+ function uploadsDirFor(accountId, subPath, attachmentId) {
4918
+ return subPath ? resolve6(DATA_ROOT, "accounts", accountId, "uploads", subPath, attachmentId) : resolve6(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
4919
+ }
4854
4920
  var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
4855
4921
  "image/jpeg",
4856
4922
  "image/png",
@@ -4875,16 +4941,16 @@ function assertSupportedMime(mimeType) {
4875
4941
  );
4876
4942
  }
4877
4943
  }
4878
- async function writeAttachment(scope, filename, mimeType, sizeBytes, buffer) {
4944
+ async function writeAttachment(accountId, subPath, filename, mimeType, sizeBytes, buffer) {
4879
4945
  const attachmentId = randomUUID4();
4880
- const dir = resolve5(ATTACHMENTS_ROOT, scope, attachmentId);
4946
+ const dir = uploadsDirFor(accountId, subPath, attachmentId);
4881
4947
  await mkdir2(dir, { recursive: true });
4882
4948
  const ext = extname(filename) || "";
4883
- const storagePath = resolve5(dir, `${attachmentId}${ext}`);
4884
- const metaPath = resolve5(dir, `${attachmentId}.meta.json`);
4949
+ const storagePath = resolve6(dir, `${attachmentId}${ext}`);
4950
+ const metaPath = resolve6(dir, `${attachmentId}.meta.json`);
4885
4951
  const meta = {
4886
4952
  attachmentId,
4887
- scope,
4953
+ scope: subPath || accountId,
4888
4954
  filename,
4889
4955
  mimeType,
4890
4956
  sizeBytes,
@@ -4902,16 +4968,16 @@ async function storeAttachment(accountId, file) {
4902
4968
  );
4903
4969
  }
4904
4970
  const buffer = Buffer.from(await file.arrayBuffer());
4905
- return writeAttachment(accountId, file.name, file.type, file.size, buffer);
4971
+ return writeAttachment(accountId, "", file.name, file.type, file.size, buffer);
4906
4972
  }
4907
- async function storePublicAttachment(sessionId, file, buffer) {
4973
+ async function storePublicAttachment(accountId, sessionId, file, buffer) {
4908
4974
  assertSupportedMime(file.type);
4909
4975
  if (file.size > MAX_FILE_SIZE_BYTES) {
4910
4976
  throw new Error(
4911
4977
  `File "${file.name}" exceeds the 50 MB limit (${(file.size / 1024 / 1024).toFixed(1)} MB).`
4912
4978
  );
4913
4979
  }
4914
- return writeAttachment(`public/${sessionId}`, file.name, file.type, file.size, buffer);
4980
+ return writeAttachment(accountId, `public/${sessionId}`, file.name, file.type, file.size, buffer);
4915
4981
  }
4916
4982
  var MIME_BY_EXT = {
4917
4983
  ".pdf": "application/pdf",
@@ -4941,8 +5007,8 @@ function detectMimeType(filePath) {
4941
5007
  return MIME_BY_EXT[ext] ?? "application/octet-stream";
4942
5008
  }
4943
5009
  function validateFilePathInAccount(filePath, accountDir) {
4944
- const resolved = realpathSync(filePath);
4945
- const accountResolved = realpathSync(accountDir);
5010
+ const resolved = realpathSync2(filePath);
5011
+ const accountResolved = realpathSync2(accountDir);
4946
5012
  if (!resolved.startsWith(accountResolved + "/")) {
4947
5013
  throw new Error(`File path is outside the account directory`);
4948
5014
  }
@@ -4955,11 +5021,11 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
4955
5021
  `Component artefact exceeds the 50 MB limit (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB).`
4956
5022
  );
4957
5023
  }
4958
- const dir = resolve5(ATTACHMENTS_ROOT, accountId, attachmentId);
5024
+ const dir = uploadsDirFor(accountId, "", attachmentId);
4959
5025
  await mkdir2(dir, { recursive: true });
4960
5026
  const ext = extname(filename) || (mimeType === "text/html" ? ".html" : ".md");
4961
- const storagePath = resolve5(dir, `${attachmentId}${ext}`);
4962
- const metaPath = resolve5(dir, `${attachmentId}.meta.json`);
5027
+ const storagePath = resolve6(dir, `${attachmentId}${ext}`);
5028
+ const metaPath = resolve6(dir, `${attachmentId}.meta.json`);
4963
5029
  const meta = {
4964
5030
  attachmentId,
4965
5031
  scope: accountId,
@@ -5063,7 +5129,7 @@ async function transcribeVoiceNote(file, source) {
5063
5129
  }
5064
5130
 
5065
5131
  // app/lib/channel-pty-bridge/bridge.ts
5066
- import { resolve as resolve7 } from "path";
5132
+ import { resolve as resolve8 } from "path";
5067
5133
 
5068
5134
  // app/lib/channel-pty-bridge/manager-client.ts
5069
5135
  function managerBase() {
@@ -5514,9 +5580,9 @@ async function fanOut(subscribers, text, onError, tag) {
5514
5580
  }
5515
5581
 
5516
5582
  // app/lib/whatsapp/outbound/send-document.ts
5517
- import { realpathSync as realpathSync2 } from "fs";
5583
+ import { realpathSync as realpathSync3 } from "fs";
5518
5584
  import { readFile, stat as stat2 } from "fs/promises";
5519
- import { resolve as resolve6, basename } from "path";
5585
+ import { resolve as resolve7, basename } from "path";
5520
5586
  var TAG16 = "[whatsapp:outbound]";
5521
5587
  var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
5522
5588
  var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
@@ -5547,11 +5613,11 @@ async function sendWhatsAppDocument(input) {
5547
5613
  if (!maxyAccountId || !platformRoot2) {
5548
5614
  return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
5549
5615
  }
5550
- const accountDir = resolve6(platformRoot2, "..", "data/accounts", maxyAccountId);
5616
+ const accountDir = resolve7(platformRoot2, "..", "data/accounts", maxyAccountId);
5551
5617
  let resolvedPath;
5552
5618
  try {
5553
- resolvedPath = realpathSync2(filePath);
5554
- const accountResolved = realpathSync2(accountDir);
5619
+ resolvedPath = realpathSync3(filePath);
5620
+ const accountResolved = realpathSync3(accountDir);
5555
5621
  if (!resolvedPath.startsWith(accountResolved + "/")) {
5556
5622
  const sanitised = filePath.replace(accountDir, "<account>/");
5557
5623
  console.error(`${TAG16} document REJECTED path=${sanitised} reason=outside_account_directory`);
@@ -5757,7 +5823,7 @@ async function ensureEntry(input) {
5757
5823
  });
5758
5824
  } else {
5759
5825
  console.error(`${tag} route role=${input.role} target=public-spawn senderId=${input.senderId}`);
5760
- const attachmentDir = resolve7(ATTACHMENTS_ROOT, "public", input.senderId);
5826
+ const attachmentDir = resolve8(DATA_ROOT, "accounts", input.accountId, "uploads", "public", input.senderId);
5761
5827
  spawned = await managerSpawn({
5762
5828
  senderId: input.senderId,
5763
5829
  role: input.role,
@@ -5889,12 +5955,12 @@ async function dispatchOnce(input) {
5889
5955
  });
5890
5956
  if (!entry) return { error: "spawn-failed" };
5891
5957
  entry.lastInboundAt = Date.now();
5892
- let resolve30;
5958
+ let resolve31;
5893
5959
  const turnPromise = new Promise((r) => {
5894
- resolve30 = r;
5960
+ resolve31 = r;
5895
5961
  });
5896
5962
  const listener = (text) => {
5897
- resolve30(text);
5963
+ resolve31(text);
5898
5964
  };
5899
5965
  entry.subscribers.add(listener);
5900
5966
  const writeAtMs = Date.now();
@@ -6043,6 +6109,10 @@ var app2 = new Hono();
6043
6109
  app2.post("/", async (c) => {
6044
6110
  console.log(`[chat-route] entered route=public method=POST`);
6045
6111
  const contentType = c.req.header("content-type") ?? "";
6112
+ const account = resolveAccount();
6113
+ if (!account) {
6114
+ return c.json({ error: "No account configured" }, 500);
6115
+ }
6046
6116
  let message;
6047
6117
  let session_key;
6048
6118
  let userTimestamp;
@@ -6103,7 +6173,7 @@ ${result.result.text}` : result.result.text;
6103
6173
  const stored = [];
6104
6174
  for (const file of regularFiles) {
6105
6175
  const buffer = Buffer.from(await file.arrayBuffer());
6106
- const att = await storePublicAttachment(session_key, file, buffer);
6176
+ const att = await storePublicAttachment(account.accountId, session_key, file, buffer);
6107
6177
  stored.push({ name: file.name, path: att.storagePath, mime: att.mimeType, sizeBytes: att.sizeBytes });
6108
6178
  }
6109
6179
  attachmentCount = stored.length;
@@ -6149,10 +6219,6 @@ ${result.result.text}` : result.result.text;
6149
6219
  );
6150
6220
  const fullMessage = message + uploadNote;
6151
6221
  const encoder = new TextEncoder();
6152
- const account = resolveAccount();
6153
- if (!account) {
6154
- return c.json({ error: "No account configured" }, 500);
6155
- }
6156
6222
  let agentSlug;
6157
6223
  let resolveSource;
6158
6224
  if (requestedSlug) {
@@ -6264,7 +6330,7 @@ ${result.result.text}` : result.result.text;
6264
6330
  var chat_default = app2;
6265
6331
 
6266
6332
  // server/routes/whatsapp.ts
6267
- import { join as join7, resolve as resolve9 } from "path";
6333
+ import { join as join7, resolve as resolve10 } from "path";
6268
6334
  import { readdirSync as readdirSync3, readFileSync as readFileSync8, existsSync as existsSync5 } from "fs";
6269
6335
 
6270
6336
  // app/lib/whatsapp/login.ts
@@ -6272,10 +6338,10 @@ import { randomUUID as randomUUID7 } from "crypto";
6272
6338
 
6273
6339
  // app/lib/whatsapp/config-persist.ts
6274
6340
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
6275
- import { resolve as resolve8, join as join5 } from "path";
6341
+ import { resolve as resolve9, join as join5 } from "path";
6276
6342
  var TAG18 = "[whatsapp:config]";
6277
6343
  function configPath(accountDir) {
6278
- return resolve8(accountDir, "account.json");
6344
+ return resolve9(accountDir, "account.json");
6279
6345
  }
6280
6346
  function readConfig(accountDir) {
6281
6347
  const path2 = configPath(accountDir);
@@ -6682,8 +6748,8 @@ async function startLogin(opts) {
6682
6748
  await clearAuth(authDir);
6683
6749
  let resolveCode = null;
6684
6750
  let rejectCode = null;
6685
- const codePromise = new Promise((resolve30, reject) => {
6686
- resolveCode = resolve30;
6751
+ const codePromise = new Promise((resolve31, reject) => {
6752
+ resolveCode = resolve31;
6687
6753
  rejectCode = reject;
6688
6754
  });
6689
6755
  const codeTimer = setTimeout(
@@ -7080,14 +7146,14 @@ app3.post("/config", async (c) => {
7080
7146
  return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
7081
7147
  }
7082
7148
  case "list-public-agents": {
7083
- const agentsDir = resolve9(account.accountDir, "agents");
7149
+ const agentsDir = resolve10(account.accountDir, "agents");
7084
7150
  const agents = [];
7085
7151
  if (existsSync5(agentsDir)) {
7086
7152
  try {
7087
7153
  const entries = readdirSync3(agentsDir, { withFileTypes: true });
7088
7154
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
7089
7155
  if (!entry.isDirectory() || entry.name === "admin") continue;
7090
- const configPath2 = resolve9(agentsDir, entry.name, "config.json");
7156
+ const configPath2 = resolve10(agentsDir, entry.name, "config.json");
7091
7157
  if (!existsSync5(configPath2)) continue;
7092
7158
  try {
7093
7159
  const config = JSON.parse(readFileSync8(configPath2, "utf-8"));
@@ -7360,15 +7426,17 @@ var whatsapp_default = app3;
7360
7426
 
7361
7427
  // server/routes/whatsapp-reader.ts
7362
7428
  import { readFileSync as readFileSync11, watch, statSync as statSync4, openSync, readSync, closeSync, existsSync as existsSync7, readdirSync as readdirSync6 } from "fs";
7363
- import { basename as basename2, dirname, join as join10, resolve as resolve11, sep as sep2 } from "path";
7429
+ import { basename as basename2, dirname, join as join10, resolve as resolve12, sep as sep3 } from "path";
7364
7430
 
7365
7431
  // server/routes/admin/sidebar-sessions.ts
7366
7432
  import { readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
7367
- import { join as join8, resolve as resolve10 } from "path";
7433
+ import { join as join8, resolve as resolve11 } from "path";
7368
7434
  var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/i;
7369
7435
  var CLI_MARKER_PREFIXES = ["<local-command-", "<command-name>", "<command-message>"];
7370
7436
  var PUBLIC_ROLE = "public";
7437
+ var ADMIN_ROLE = "admin";
7371
7438
  var WEBCHAT_CHANNEL = "webchat";
7439
+ var WHATSAPP_CHANNEL = "whatsapp";
7372
7440
  var app4 = new Hono();
7373
7441
  function claudeConfigDir() {
7374
7442
  return process.env.CLAUDE_CONFIG_DIR ?? null;
@@ -7481,7 +7549,7 @@ function loadUserTitles(accountDir) {
7481
7549
  return out;
7482
7550
  }
7483
7551
  function readSidecarMeta(metaPath) {
7484
- const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null };
7552
+ const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null, personId: null, adminUserId: null };
7485
7553
  let raw;
7486
7554
  try {
7487
7555
  raw = readFileSync9(metaPath, "utf8");
@@ -7502,7 +7570,9 @@ function readSidecarMeta(metaPath) {
7502
7570
  role: typeof r.role === "string" ? r.role : null,
7503
7571
  channel: typeof r.channel === "string" ? r.channel : null,
7504
7572
  senderId: typeof r.senderId === "string" ? r.senderId : null,
7505
- startedAt: typeof r.startedAt === "string" ? r.startedAt : null
7573
+ startedAt: typeof r.startedAt === "string" ? r.startedAt : null,
7574
+ personId: typeof r.personId === "string" ? r.personId : null,
7575
+ adminUserId: typeof r.adminUserId === "string" ? r.adminUserId : null
7506
7576
  };
7507
7577
  }
7508
7578
  function startsWithCliMarker(s) {
@@ -7512,9 +7582,29 @@ function startsWithCliMarker(s) {
7512
7582
  function trimToTitle(raw) {
7513
7583
  return raw.trim().replace(/\s+/g, " ");
7514
7584
  }
7585
+ var CHANNEL_WRAPPER = /^<channel\b([^>]*)>\n?([\s\S]*?)\n?<\/channel>$/;
7586
+ var SOURCE_ATTR = /\bsource="([^"]+)"/;
7587
+ var CHANNEL_SOURCE_MAP = {
7588
+ whatsapp: "whatsapp",
7589
+ "whatsapp-channel": "whatsapp",
7590
+ webchat: "webchat",
7591
+ "webchat-channel": "webchat"
7592
+ };
7593
+ function stripChannelEnvelope(text) {
7594
+ const m = text.match(CHANNEL_WRAPPER);
7595
+ if (!m) return { inner: text, channel: null };
7596
+ let raw = m[1].match(SOURCE_ATTR)?.[1] ?? "";
7597
+ let inner = m[2];
7598
+ const im = inner.match(CHANNEL_WRAPPER);
7599
+ const innerRaw = im?.[1].match(SOURCE_ATTR)?.[1];
7600
+ if (im && innerRaw) {
7601
+ raw = innerRaw;
7602
+ inner = im[2];
7603
+ }
7604
+ return { inner, channel: CHANNEL_SOURCE_MAP[raw] ?? (raw || null) };
7605
+ }
7515
7606
  function resolveTitle(body, sessionId, userTitles) {
7516
7607
  const userTitle = userTitles.get(sessionId);
7517
- if (userTitle) return { title: trimToTitle(userTitle), source: "user" };
7518
7608
  let aiTitle = null;
7519
7609
  let firstUserMessage = null;
7520
7610
  for (const line of body.split("\n")) {
@@ -7541,9 +7631,12 @@ function resolveTitle(body, sessionId, userTitles) {
7541
7631
  firstUserMessage = trimmed;
7542
7632
  }
7543
7633
  }
7544
- if (aiTitle) return { title: trimToTitle(aiTitle), source: "ai" };
7545
- if (firstUserMessage) return { title: trimToTitle(firstUserMessage), source: "first-message" };
7546
- return { title: sessionId.slice(0, 8), source: "prefix" };
7634
+ const stripped = firstUserMessage !== null ? stripChannelEnvelope(firstUserMessage) : { inner: null, channel: null };
7635
+ const channel = stripped.channel;
7636
+ if (userTitle) return { title: trimToTitle(userTitle), source: "user", channel };
7637
+ if (aiTitle) return { title: trimToTitle(aiTitle), source: "ai", channel };
7638
+ if (stripped.inner) return { title: trimToTitle(stripped.inner), source: "first-message", channel };
7639
+ return { title: sessionId.slice(0, 8), source: "prefix", channel };
7547
7640
  }
7548
7641
  app4.get("/", requireAdminSession, async (c) => {
7549
7642
  const accountId = process.env.ACCOUNT_ID ?? null;
@@ -7555,7 +7648,7 @@ app4.get("/", requireAdminSession, async (c) => {
7555
7648
  const projectsRoot = join8(configDir2, "projects");
7556
7649
  const jsonls = enumerateJsonls(projectsRoot);
7557
7650
  const liveSessions = await fetchLiveSessions();
7558
- const accountDir = accountId ? resolve10(ACCOUNTS_DIR, accountId) : null;
7651
+ const accountDir = accountId ? resolve11(ACCOUNTS_DIR, accountId) : null;
7559
7652
  const userTitles = loadUserTitles(accountDir);
7560
7653
  const rows = [];
7561
7654
  const seenIds = /* @__PURE__ */ new Set();
@@ -7564,6 +7657,8 @@ app4.get("/", requireAdminSession, async (c) => {
7564
7657
  let nonResumableCount = 0;
7565
7658
  let archivedCount = 0;
7566
7659
  const titleSourceCounts = { user: 0, ai: 0, "first-message": 0, prefix: 0 };
7660
+ const personIdBySession = /* @__PURE__ */ new Map();
7661
+ const adminUserIdBySession = /* @__PURE__ */ new Map();
7567
7662
  for (const { path: path2, isSubagent, archived } of jsonls) {
7568
7663
  const lastSlash = path2.lastIndexOf("/");
7569
7664
  const base = path2.slice(lastSlash + 1);
@@ -7587,8 +7682,12 @@ app4.get("/", requireAdminSession, async (c) => {
7587
7682
  const resolved = resolveTitle(body, sessionId, userTitles);
7588
7683
  titleSourceCounts[resolved.source] += 1;
7589
7684
  const metaPath = join8(projectDir, `${sessionId}.meta.json`);
7590
- const { bridgeIds, role, channel } = readSidecarMeta(metaPath);
7591
- const resumable = !(role === PUBLIC_ROLE && channel === WEBCHAT_CHANNEL);
7685
+ const { bridgeIds, role, channel: sidecarChannel, personId, adminUserId } = readSidecarMeta(metaPath);
7686
+ const resumable = !(role === PUBLIC_ROLE && (sidecarChannel === WEBCHAT_CHANNEL || sidecarChannel === WHATSAPP_CHANNEL));
7687
+ if (personId) personIdBySession.set(sessionId, personId);
7688
+ if (adminUserId && role === ADMIN_ROLE && sidecarChannel === WEBCHAT_CHANNEL) {
7689
+ adminUserIdBySession.set(sessionId, adminUserId);
7690
+ }
7592
7691
  rows.push({
7593
7692
  sessionId,
7594
7693
  title: resolved.title,
@@ -7600,30 +7699,59 @@ app4.get("/", requireAdminSession, async (c) => {
7600
7699
  projectDir,
7601
7700
  bridgeIds,
7602
7701
  resumable,
7603
- archived
7702
+ archived,
7703
+ channel: resolved.channel,
7704
+ personName: null
7604
7705
  });
7605
7706
  if (live) liveCount += 1;
7606
7707
  if (isSubagent) subagentCount += 1;
7607
7708
  if (!resumable) nonResumableCount += 1;
7608
7709
  if (archived) archivedCount += 1;
7609
7710
  }
7711
+ let personNameCount = 0;
7712
+ let adminNameCount = 0;
7713
+ if (accountId) {
7714
+ await Promise.all(
7715
+ rows.map(async (row) => {
7716
+ if (row.titleSource !== "first-message" && row.titleSource !== "prefix") return;
7717
+ const pid = personIdBySession.get(row.sessionId);
7718
+ if (pid) {
7719
+ const name = await loadPersonNameByElementId(accountId, pid);
7720
+ if (name) {
7721
+ row.personName = name;
7722
+ personNameCount += 1;
7723
+ }
7724
+ return;
7725
+ }
7726
+ const auid = adminUserIdBySession.get(row.sessionId);
7727
+ if (auid) {
7728
+ const resolved = await loadAdminUserName(accountId, auid);
7729
+ if (resolved.source === "neo4j") {
7730
+ row.personName = resolved.joined;
7731
+ adminNameCount += 1;
7732
+ }
7733
+ }
7734
+ })
7735
+ );
7736
+ }
7610
7737
  rows.sort((a, b) => a.startedAt < b.startedAt ? 1 : -1);
7611
7738
  console.log(
7612
- `[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} archived=${archivedCount} nonResumable=${nonResumableCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} accountId=${accountId ? accountId.slice(0, 8) : "unset"}`
7739
+ `[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} archived=${archivedCount} nonResumable=${nonResumableCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} personNames=${personNameCount} adminNames=${adminNameCount} accountId=${accountId ? accountId.slice(0, 8) : "unset"}`
7613
7740
  );
7614
7741
  return c.json({ sessions: rows, accountId });
7615
7742
  });
7616
7743
  var sidebar_sessions_default = app4;
7617
7744
 
7618
7745
  // app/lib/whatsapp-reader/select-sessions.ts
7619
- function selectAdminChannelSessions(rows) {
7620
- return rows.filter((r) => r.role === "admin" && (r.channel === "whatsapp" || r.channel === "telegram")).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).map((r) => ({
7746
+ function selectReaderChannelSessions(rows) {
7747
+ return rows.filter((r) => r.role === "admin" && (r.channel === "whatsapp" || r.channel === "telegram") || r.role === "public" && (r.channel === "whatsapp" || r.channel === "webchat")).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).map((r) => ({
7621
7748
  sessionId: r.sessionId,
7622
7749
  projectDir: r.projectDir,
7623
7750
  title: r.title,
7624
7751
  senderId: r.senderId,
7625
7752
  startedAt: r.startedAt,
7626
7753
  channel: r.channel,
7754
+ role: r.role,
7627
7755
  operatorName: null,
7628
7756
  whatsappName: null,
7629
7757
  lastMessageAt: r.lastMessageAt
@@ -7727,14 +7855,26 @@ function resolveOperatorDisplay(userIdRes, nameRes) {
7727
7855
  return { name: null, reason: "no-person-name" };
7728
7856
  }
7729
7857
 
7858
+ // app/lib/agent-errors.ts
7859
+ function classifyAgentError(err) {
7860
+ const msg = err instanceof Error ? err.message : String(err);
7861
+ if (/\b(401|authentication_error|OAuth token has expired)\b/i.test(msg)) return "auth_expired";
7862
+ if (/\b(529|overloaded)\b/i.test(msg)) return "claude_overloaded";
7863
+ if (/\b(404|model_not_found)\b/i.test(msg) || /issue with the selected model|may not have access to it/i.test(msg))
7864
+ return "model_unavailable";
7865
+ if (/\b(500|api_error|internal server error)\b/i.test(msg)) return "claude_down";
7866
+ return "agent_error";
7867
+ }
7868
+
7730
7869
  // app/lib/whatsapp-reader/parse-transcript.ts
7731
7870
  var WA_REPLY_TOOL = "mcp__whatsapp-channel__reply";
7732
7871
  var WA_REPLY_DOCUMENT_TOOL = "mcp__whatsapp-channel__reply-document";
7733
7872
  var WEBCHAT_REPLY_TOOL = "mcp__webchat-channel__reply";
7734
7873
  var CHANNEL_REPLY_TOOL = "mcp__channel__reply";
7874
+ var CHANNEL_REPLY_DOCUMENT_TOOL = "mcp__channel__reply-document";
7735
7875
  var CLI_MARKER_PREFIXES2 = ["<local-command-", "<command-name>", "<command-message>"];
7736
- var CHANNEL_WRAPPER = /^<channel\b([^>]*)>\n?([\s\S]*?)\n?<\/channel>$/;
7737
- var SOURCE_ATTR = /\bsource="([^"]+)"/;
7876
+ var CHANNEL_WRAPPER2 = /^<channel\b([^>]*)>\n?([\s\S]*?)\n?<\/channel>$/;
7877
+ var SOURCE_ATTR2 = /\bsource="([^"]+)"/;
7738
7878
  var SOURCE_MAP = {
7739
7879
  whatsapp: "whatsapp",
7740
7880
  "whatsapp-channel": "whatsapp",
@@ -7742,13 +7882,13 @@ var SOURCE_MAP = {
7742
7882
  "webchat-channel": "webchat"
7743
7883
  };
7744
7884
  function unwrapChannel(text) {
7745
- const m = text.match(CHANNEL_WRAPPER);
7885
+ const m = text.match(CHANNEL_WRAPPER2);
7746
7886
  if (!m) return { text };
7747
- let source = SOURCE_MAP[m[1].match(SOURCE_ATTR)?.[1] ?? ""];
7887
+ let source = SOURCE_MAP[m[1].match(SOURCE_ATTR2)?.[1] ?? ""];
7748
7888
  let inner = m[2];
7749
- const im = inner.match(CHANNEL_WRAPPER);
7889
+ const im = inner.match(CHANNEL_WRAPPER2);
7750
7890
  if (im) {
7751
- const innerSource = SOURCE_MAP[im[1].match(SOURCE_ATTR)?.[1] ?? ""];
7891
+ const innerSource = SOURCE_MAP[im[1].match(SOURCE_ATTR2)?.[1] ?? ""];
7752
7892
  if (innerSource) {
7753
7893
  source = innerSource;
7754
7894
  inner = im[2];
@@ -7762,6 +7902,17 @@ function asString(content) {
7762
7902
  function tsOf(row) {
7763
7903
  return typeof row.timestamp === "string" ? row.timestamp : null;
7764
7904
  }
7905
+ function joinTextBlocks(content) {
7906
+ if (!Array.isArray(content)) return "";
7907
+ const parts = [];
7908
+ for (const block of content) {
7909
+ if (block && typeof block === "object") {
7910
+ const b = block;
7911
+ if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
7912
+ }
7913
+ }
7914
+ return parts.join("");
7915
+ }
7765
7916
  function assistantTurns(content, ts) {
7766
7917
  if (!Array.isArray(content)) return [];
7767
7918
  const out = [];
@@ -7773,7 +7924,7 @@ function assistantTurns(content, ts) {
7773
7924
  } else if (b.type === "tool_use" && (b.name === WA_REPLY_TOOL || b.name === WEBCHAT_REPLY_TOOL || b.name === CHANNEL_REPLY_TOOL)) {
7774
7925
  const text = b.input?.text;
7775
7926
  out.push({ kind: "agent-reply", text: typeof text === "string" ? text : "", ts });
7776
- } else if (b.type === "tool_use" && b.name === WA_REPLY_DOCUMENT_TOOL) {
7927
+ } else if (b.type === "tool_use" && (b.name === WA_REPLY_DOCUMENT_TOOL || b.name === CHANNEL_REPLY_DOCUMENT_TOOL)) {
7777
7928
  const input = b.input ?? {};
7778
7929
  const files = Array.isArray(input.files) ? input.files.filter((f) => typeof f === "string") : [];
7779
7930
  const caption = typeof input.caption === "string" ? input.caption : null;
@@ -7819,6 +7970,14 @@ function parseTranscript(lines) {
7819
7970
  const ts = tsOf(row);
7820
7971
  const msg = row.message;
7821
7972
  if (row.type === "assistant") {
7973
+ if (row.isApiErrorMessage === true) {
7974
+ const raw = joinTextBlocks(msg?.content);
7975
+ const status = typeof row.apiErrorStatus === "number" ? String(row.apiErrorStatus) : "";
7976
+ const errorSlug = typeof row.error === "string" ? row.error : "";
7977
+ const code = classifyAgentError(`${status} ${errorSlug} ${raw}`.trim());
7978
+ out.push({ kind: "agent-error", code, raw, ts });
7979
+ continue;
7980
+ }
7822
7981
  out.push(...assistantTurns(msg?.content, ts));
7823
7982
  continue;
7824
7983
  }
@@ -7882,7 +8041,9 @@ app5.get("/conversations", requireAdminSession, async (c) => {
7882
8041
  const sessionId = basename2(path2).replace(/\.jsonl$/, "");
7883
8042
  const projectDir = dirname(path2);
7884
8043
  const meta = readSidecarMeta(join10(projectDir, `${sessionId}.meta.json`));
7885
- if (meta.role !== "admin" || meta.channel !== "whatsapp" && meta.channel !== "telegram") continue;
8044
+ const isAdminChannel = meta.role === "admin" && (meta.channel === "whatsapp" || meta.channel === "telegram");
8045
+ const isPublicChannel = meta.role === "public" && (meta.channel === "whatsapp" || meta.channel === "webchat");
8046
+ if (!isAdminChannel && !isPublicChannel) continue;
7886
8047
  let body = "";
7887
8048
  try {
7888
8049
  body = readFileSync11(path2, "utf8");
@@ -7901,7 +8062,7 @@ app5.get("/conversations", requireAdminSession, async (c) => {
7901
8062
  lastMessageAt
7902
8063
  });
7903
8064
  }
7904
- const selected = selectAdminChannelSessions(rows);
8065
+ const selected = selectReaderChannelSessions(rows);
7905
8066
  const account = resolveAccount();
7906
8067
  let users = [];
7907
8068
  if (account) {
@@ -7968,12 +8129,12 @@ app5.get("/stream", requireAdminSession, (c) => {
7968
8129
  const sessionId = c.req.query("sessionId") ?? "";
7969
8130
  const projectDir = c.req.query("projectDir") ?? "";
7970
8131
  const cfg = claudeConfigDir();
7971
- const projectsRoot = cfg ? resolve11(join10(cfg, "projects")) : null;
8132
+ const projectsRoot = cfg ? resolve12(join10(cfg, "projects")) : null;
7972
8133
  if (!cfg || !projectsRoot || !SESSION_ID_RE2.test(sessionId)) {
7973
8134
  return c.json({ error: "bad session reference" }, 400);
7974
8135
  }
7975
- const jsonlPath = resolve11(join10(projectDir, `${sessionId}.jsonl`));
7976
- if (!jsonlPath.startsWith(projectsRoot + sep2)) {
8136
+ const jsonlPath = resolve12(join10(projectDir, `${sessionId}.jsonl`));
8137
+ if (!jsonlPath.startsWith(projectsRoot + sep3)) {
7977
8138
  return c.json({ error: "bad session reference" }, 400);
7978
8139
  }
7979
8140
  const encoder = new TextEncoder();
@@ -8004,6 +8165,9 @@ data: ${JSON.stringify(turn)}
8004
8165
  let n = 0;
8005
8166
  for (const turn of parseTranscript(lines)) {
8006
8167
  send(controller, turn, offset);
8168
+ if (turn.kind === "agent-error") {
8169
+ console.log(`[wa-stream] op=agent-error conn=${connId} sessionId=${sessionId} code=${turn.code} source=jsonl`);
8170
+ }
8007
8171
  n++;
8008
8172
  }
8009
8173
  if (n > 0) console.log(`[wa-stream] op=drain conn=${connId} turns=${n}`);
@@ -8017,6 +8181,9 @@ data: ${JSON.stringify(turn)}
8017
8181
  let n = 0;
8018
8182
  for (const turn of parseTranscript(buf.toString("utf8").split("\n"))) {
8019
8183
  send(controller, turn, offset);
8184
+ if (turn.kind === "agent-error") {
8185
+ console.log(`[wa-stream] op=agent-error conn=${connId} sessionId=${sessionId} code=${turn.code} source=jsonl`);
8186
+ }
8020
8187
  n++;
8021
8188
  }
8022
8189
  console.log(`[wa-stream] op=backlog conn=${connId} turns=${n} bytes=${end}`);
@@ -8059,7 +8226,7 @@ var DIRECTIVE_NAME_RE = /^\d+-\d+\.txt$/;
8059
8226
  function directiveStoreDir(sessionId) {
8060
8227
  const account = resolveAccount();
8061
8228
  if (!account) return null;
8062
- return resolve11(account.accountDir, "logs", "prompt-optimiser-directives", sessionId);
8229
+ return resolve12(account.accountDir, "logs", "prompt-optimiser-directives", sessionId);
8063
8230
  }
8064
8231
  app5.get("/directives", requireAdminSession, (c) => {
8065
8232
  const sessionId = c.req.query("sessionId") ?? "";
@@ -8093,8 +8260,8 @@ app5.get("/directive", requireAdminSession, (c) => {
8093
8260
  }
8094
8261
  const dir = directiveStoreDir(sessionId);
8095
8262
  if (!dir) return c.json({ error: "no account" }, 404);
8096
- const path2 = resolve11(join10(dir, name));
8097
- if (!path2.startsWith(dir + sep2)) return c.json({ error: "bad reference" }, 400);
8263
+ const path2 = resolve12(join10(dir, name));
8264
+ if (!path2.startsWith(dir + sep3)) return c.json({ error: "bad reference" }, 400);
8098
8265
  let body;
8099
8266
  try {
8100
8267
  body = readFileSync11(path2, "utf8");
@@ -8430,7 +8597,7 @@ ${note}` : note;
8430
8597
  }
8431
8598
 
8432
8599
  // server/routes/webchat-greeting.ts
8433
- import { resolve as resolve12 } from "path";
8600
+ import { resolve as resolve13 } from "path";
8434
8601
 
8435
8602
  // app/lib/claude-agent/specialist-roster.ts
8436
8603
  import { existsSync as existsSync9, readFileSync as readFileSync13, readdirSync as readdirSync8 } from "fs";
@@ -8535,7 +8702,7 @@ app6.get("/", requireAdminSession, async (c) => {
8535
8702
  let specialists = [];
8536
8703
  try {
8537
8704
  const roster = readSpecialistRoster(
8538
- resolve12(ACCOUNTS_DIR, accountId, "specialists", "agents")
8705
+ resolve13(ACCOUNTS_DIR, accountId, "specialists", "agents")
8539
8706
  );
8540
8707
  specialists = roster.specialists;
8541
8708
  for (const s of roster.skipped) {
@@ -8570,7 +8737,7 @@ var webchat_greeting_default = app6;
8570
8737
 
8571
8738
  // server/routes/telegram.ts
8572
8739
  import { homedir } from "os";
8573
- import { resolve as resolve13, join as join13 } from "path";
8740
+ import { resolve as resolve14, join as join13 } from "path";
8574
8741
  import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
8575
8742
 
8576
8743
  // app/lib/telegram/access-control.ts
@@ -8621,7 +8788,7 @@ function routeTelegramUpdate(input) {
8621
8788
  var TAG22 = "[telegram-inbound]";
8622
8789
  var DISPATCH_TIMEOUT_MS = 12e4;
8623
8790
  function configDirName() {
8624
- const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "..");
8791
+ const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
8625
8792
  const brandPath = join13(platformRoot2, "config", "brand.json");
8626
8793
  if (existsSync10(brandPath)) {
8627
8794
  try {
@@ -8633,7 +8800,7 @@ function configDirName() {
8633
8800
  }
8634
8801
  function secretPath(botType) {
8635
8802
  const filename = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
8636
- return resolve13(homedir(), configDirName(), filename);
8803
+ return resolve14(homedir(), configDirName(), filename);
8637
8804
  }
8638
8805
  async function sendTelegram(botToken, chatId, text) {
8639
8806
  try {
@@ -9448,7 +9615,7 @@ var session_default = app10;
9448
9615
 
9449
9616
  // server/routes/admin/logs.ts
9450
9617
  import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as readFileSync17, statSync as statSync7 } from "fs";
9451
- import { resolve as resolve14, basename as basename4 } from "path";
9618
+ import { resolve as resolve15, basename as basename4 } from "path";
9452
9619
 
9453
9620
  // app/lib/logs-read-resolve.ts
9454
9621
  import { existsSync as existsSync14 } from "fs";
@@ -9475,7 +9642,7 @@ app11.get("/", async (c) => {
9475
9642
  const cacheKeyParam = c.req.query("cacheKey");
9476
9643
  const download = c.req.query("download") === "1";
9477
9644
  const account = resolveAccount();
9478
- const accountLogDir = account ? resolve14(account.accountDir, "logs") : null;
9645
+ const accountLogDir = account ? resolve15(account.accountDir, "logs") : null;
9479
9646
  const logDirs = [];
9480
9647
  if (accountLogDir) logDirs.push(accountLogDir);
9481
9648
  logDirs.push(LOG_DIR);
@@ -9483,7 +9650,7 @@ app11.get("/", async (c) => {
9483
9650
  const safe = basename4(fileParam);
9484
9651
  const searched = [];
9485
9652
  for (const dir of logDirs) {
9486
- const filePath = resolve14(dir, safe);
9653
+ const filePath = resolve15(dir, safe);
9487
9654
  searched.push(filePath);
9488
9655
  try {
9489
9656
  const buffer = readFileSync17(filePath);
@@ -9595,10 +9762,10 @@ app11.get("/", async (c) => {
9595
9762
  console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
9596
9763
  continue;
9597
9764
  }
9598
- files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(resolve14(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
9765
+ files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(resolve15(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
9599
9766
  seen.add(name);
9600
9767
  try {
9601
- const content = readFileSync17(resolve14(dir, name));
9768
+ const content = readFileSync17(resolve15(dir, name));
9602
9769
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
9603
9770
  logs[name] = tail.trim() || "(empty)";
9604
9771
  } catch (err) {
@@ -9638,7 +9805,7 @@ var claude_info_default = app12;
9638
9805
  // server/routes/admin/attachment.ts
9639
9806
  import { readFile as readFile2, readdir } from "fs/promises";
9640
9807
  import { existsSync as existsSync16 } from "fs";
9641
- import { resolve as resolve15 } from "path";
9808
+ import { resolve as resolve16 } from "path";
9642
9809
  var app13 = new Hono();
9643
9810
  app13.get("/:attachmentId", requireAdminSession, async (c) => {
9644
9811
  const attachmentId = c.req.param("attachmentId");
@@ -9650,11 +9817,11 @@ app13.get("/:attachmentId", requireAdminSession, async (c) => {
9650
9817
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
9651
9818
  return new Response("Not found", { status: 404 });
9652
9819
  }
9653
- const dir = resolve15(ATTACHMENTS_ROOT, accountId, attachmentId);
9820
+ const dir = resolve16(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
9654
9821
  if (!existsSync16(dir)) {
9655
9822
  return new Response("Not found", { status: 404 });
9656
9823
  }
9657
- const metaPath = resolve15(dir, `${attachmentId}.meta.json`);
9824
+ const metaPath = resolve16(dir, `${attachmentId}.meta.json`);
9658
9825
  if (!existsSync16(metaPath)) {
9659
9826
  return new Response("Not found", { status: 404 });
9660
9827
  }
@@ -9669,7 +9836,7 @@ app13.get("/:attachmentId", requireAdminSession, async (c) => {
9669
9836
  if (!dataFile) {
9670
9837
  return new Response("Not found", { status: 404 });
9671
9838
  }
9672
- const filePath = resolve15(dir, dataFile);
9839
+ const filePath = resolve16(dir, dataFile);
9673
9840
  const buffer = await readFile2(filePath);
9674
9841
  return new Response(new Uint8Array(buffer), {
9675
9842
  headers: {
@@ -9682,13 +9849,13 @@ app13.get("/:attachmentId", requireAdminSession, async (c) => {
9682
9849
  var attachment_default = app13;
9683
9850
 
9684
9851
  // server/routes/admin/agents.ts
9685
- import { resolve as resolve16 } from "path";
9852
+ import { resolve as resolve17 } from "path";
9686
9853
  import { readdirSync as readdirSync11, readFileSync as readFileSync18, existsSync as existsSync17, rmSync } from "fs";
9687
9854
  var app14 = new Hono();
9688
9855
  app14.get("/", (c) => {
9689
9856
  const account = resolveAccount();
9690
9857
  if (!account) return c.json({ agents: [] });
9691
- const agentsDir = resolve16(account.accountDir, "agents");
9858
+ const agentsDir = resolve17(account.accountDir, "agents");
9692
9859
  if (!existsSync17(agentsDir)) return c.json({ agents: [] });
9693
9860
  const agents = [];
9694
9861
  try {
@@ -9696,7 +9863,7 @@ app14.get("/", (c) => {
9696
9863
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
9697
9864
  if (!entry.isDirectory()) continue;
9698
9865
  if (entry.name === "admin") continue;
9699
- const configPath2 = resolve16(agentsDir, entry.name, "config.json");
9866
+ const configPath2 = resolve17(agentsDir, entry.name, "config.json");
9700
9867
  if (!existsSync17(configPath2)) continue;
9701
9868
  try {
9702
9869
  const config = JSON.parse(readFileSync18(configPath2, "utf-8"));
@@ -9725,7 +9892,7 @@ app14.delete("/:slug", async (c) => {
9725
9892
  if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
9726
9893
  return c.json({ error: "Invalid agent slug" }, 400);
9727
9894
  }
9728
- const agentDir = resolve16(account.accountDir, "agents", slug);
9895
+ const agentDir = resolve17(account.accountDir, "agents", slug);
9729
9896
  if (!existsSync17(agentDir)) {
9730
9897
  return c.json({ error: "Agent not found" }, 404);
9731
9898
  }
@@ -9755,7 +9922,7 @@ app14.post("/:slug/project", async (c) => {
9755
9922
  if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
9756
9923
  return c.json({ error: "Invalid agent slug" }, 400);
9757
9924
  }
9758
- const agentDir = resolve16(account.accountDir, "agents", slug);
9925
+ const agentDir = resolve17(account.accountDir, "agents", slug);
9759
9926
  if (!existsSync17(agentDir)) {
9760
9927
  return c.json({ error: "Agent not found on disk" }, 404);
9761
9928
  }
@@ -10459,7 +10626,7 @@ var sessions_default = app15;
10459
10626
 
10460
10627
  // app/lib/claude-agent/spawn-context.ts
10461
10628
  import { existsSync as existsSync19, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync8 } from "fs";
10462
- import { dirname as dirname4, resolve as resolve17, join as join17 } from "path";
10629
+ import { dirname as dirname4, resolve as resolve18, join as join17 } from "path";
10463
10630
  async function resolveOwnerProfileBlock(accountId, userId) {
10464
10631
  if (!userId) return { ok: false, reason: "missing-user-id" };
10465
10632
  try {
@@ -10544,7 +10711,7 @@ function parseSkillPathsFromFrontmatter(fm) {
10544
10711
  }
10545
10712
  function listPluginDirs() {
10546
10713
  const out = /* @__PURE__ */ new Map();
10547
- const platformPlugins = resolve17(PLATFORM_ROOT, "plugins");
10714
+ const platformPlugins = resolve18(PLATFORM_ROOT, "plugins");
10548
10715
  if (existsSync19(platformPlugins)) {
10549
10716
  for (const name of readdirSync12(platformPlugins)) {
10550
10717
  const dir = join17(platformPlugins, name);
@@ -10554,7 +10721,7 @@ function listPluginDirs() {
10554
10721
  }
10555
10722
  }
10556
10723
  }
10557
- const premiumRoot = resolve17(dirname4(PLATFORM_ROOT), "premium-plugins");
10724
+ const premiumRoot = resolve18(dirname4(PLATFORM_ROOT), "premium-plugins");
10558
10725
  if (existsSync19(premiumRoot)) {
10559
10726
  for (const bundle of readdirSync12(premiumRoot)) {
10560
10727
  const bundlePlugins = join17(premiumRoot, bundle, "plugins");
@@ -10574,7 +10741,7 @@ function listPluginDirs() {
10574
10741
  return out;
10575
10742
  }
10576
10743
  function readEnabledPlugins(accountId) {
10577
- const accountFile = resolve17(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
10744
+ const accountFile = resolve18(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
10578
10745
  if (!existsSync19(accountFile)) return /* @__PURE__ */ new Set();
10579
10746
  try {
10580
10747
  const parsed = JSON.parse(readFileSync19(accountFile, "utf-8"));
@@ -10647,7 +10814,7 @@ ${skillFm}
10647
10814
  return out;
10648
10815
  }
10649
10816
  function computeSpecialistDomains(accountId) {
10650
- const specialistsDir = resolve17(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
10817
+ const specialistsDir = resolve18(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
10651
10818
  if (!existsSync19(specialistsDir)) return [];
10652
10819
  let entries;
10653
10820
  try {
@@ -10693,7 +10860,7 @@ ${fm}
10693
10860
  return out;
10694
10861
  }
10695
10862
  function computeDormantPlugins(accountId) {
10696
- const accountFile = resolve17(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
10863
+ const accountFile = resolve18(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
10697
10864
  if (!existsSync19(accountFile)) return [];
10698
10865
  let enabled;
10699
10866
  try {
@@ -10708,7 +10875,7 @@ function computeDormantPlugins(accountId) {
10708
10875
  );
10709
10876
  return [];
10710
10877
  }
10711
- const pluginsDir = resolve17(PLATFORM_ROOT, "plugins");
10878
+ const pluginsDir = resolve18(PLATFORM_ROOT, "plugins");
10712
10879
  if (!existsSync19(pluginsDir)) return [];
10713
10880
  let entries;
10714
10881
  try {
@@ -10719,7 +10886,7 @@ function computeDormantPlugins(accountId) {
10719
10886
  const out = [];
10720
10887
  for (const name of entries) {
10721
10888
  if (enabled.has(name)) continue;
10722
- const manifestPath = resolve17(pluginsDir, name, "PLUGIN.md");
10889
+ const manifestPath = resolve18(pluginsDir, name, "PLUGIN.md");
10723
10890
  if (!existsSync19(manifestPath)) continue;
10724
10891
  let manifest;
10725
10892
  try {
@@ -10738,7 +10905,7 @@ function computeDormantPlugins(accountId) {
10738
10905
 
10739
10906
  // server/routes/admin/claude-sessions.ts
10740
10907
  import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
10741
- import { resolve as resolve18 } from "path";
10908
+ import { resolve as resolve19 } from "path";
10742
10909
  function readTunnelState(brandConfigDir) {
10743
10910
  const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
10744
10911
  if (!existsSync20(statePath)) return null;
@@ -10754,10 +10921,10 @@ function readTunnelState(brandConfigDir) {
10754
10921
  }
10755
10922
  }
10756
10923
  function resolveTunnelUrl() {
10757
- const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve18(process.cwd(), "..");
10924
+ const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve19(process.cwd(), "..");
10758
10925
  let brand;
10759
10926
  try {
10760
- brand = JSON.parse(readFileSync20(resolve18(platformRoot2, "config", "brand.json"), "utf-8"));
10927
+ brand = JSON.parse(readFileSync20(resolve19(platformRoot2, "config", "brand.json"), "utf-8"));
10761
10928
  } catch {
10762
10929
  return null;
10763
10930
  }
@@ -11005,68 +11172,6 @@ import { realpathSync as realpathSync4 } from "fs";
11005
11172
  import { basename as basename6, dirname as dirname5, join as join19, resolve as resolve21, sep as sep5 } from "path";
11006
11173
  import { Readable as Readable2 } from "stream";
11007
11174
 
11008
- // app/lib/data-path.ts
11009
- import { realpathSync as realpathSync3 } from "fs";
11010
- import { resolve as resolve19, normalize, sep as sep3, relative } from "path";
11011
- var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve19(process.cwd(), "../platform");
11012
- var DATA_ROOT = resolve19(PLATFORM_ROOT6, "..", "data");
11013
- var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve19(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
11014
- var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["uploads", "accounts"]);
11015
- var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11016
- function crossesForeignAccountPartition(relPath, accountId) {
11017
- const segs = relPath.split("/").filter(Boolean);
11018
- if (segs.length < 2) return false;
11019
- if (!ACCOUNT_PARTITION_DIRS.has(segs[0])) return false;
11020
- const owner = segs[1];
11021
- if (!ACCOUNT_UUID_RE.test(owner)) return false;
11022
- return owner !== accountId;
11023
- }
11024
- function resolveUnderRoot(raw, rootAbs, rootLabel) {
11025
- const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
11026
- const absolute = resolve19(rootAbs, cleaned);
11027
- let rootReal;
11028
- try {
11029
- rootReal = realpathSync3(rootAbs);
11030
- } catch (err) {
11031
- return {
11032
- ok: false,
11033
- status: 500,
11034
- error: `${rootLabel} not accessible: ${err instanceof Error ? err.message : String(err)}`
11035
- };
11036
- }
11037
- let resolvedReal;
11038
- try {
11039
- resolvedReal = realpathSync3(absolute);
11040
- } catch (err) {
11041
- const code = err.code;
11042
- if (code === "ENOENT") {
11043
- if (absolute !== rootReal && !absolute.startsWith(rootReal + sep3)) {
11044
- return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: absolute };
11045
- }
11046
- return { ok: false, status: 404, error: "Not found" };
11047
- }
11048
- return {
11049
- ok: false,
11050
- status: 500,
11051
- error: err instanceof Error ? err.message : String(err)
11052
- };
11053
- }
11054
- if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + sep3)) {
11055
- return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: resolvedReal };
11056
- }
11057
- const relPath = relative(rootReal, resolvedReal) || ".";
11058
- return { ok: true, absolute: resolvedReal, dataRootReal: rootReal, relative: relPath };
11059
- }
11060
- function resolveDataPath(raw) {
11061
- return resolveUnderRoot(raw, DATA_ROOT, "DATA_ROOT");
11062
- }
11063
- function resolveClaudeUploadsPath(raw) {
11064
- if (!CLAUDE_UPLOADS_ROOT) {
11065
- return { ok: false, status: 404, error: "Not found" };
11066
- }
11067
- return resolveUnderRoot(raw, CLAUDE_UPLOADS_ROOT, "CLAUDE_UPLOADS_ROOT");
11068
- }
11069
-
11070
11175
  // ../lib/graph-trash/src/index.ts
11071
11176
  var UNIQUE_KEYS_BY_LABEL = {
11072
11177
  Person: ["email", "telephone"],
@@ -11304,11 +11409,11 @@ async function restoreNode(params) {
11304
11409
  var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11305
11410
  function parseAttachmentPath(relPath) {
11306
11411
  const segments = relPath.split("/").filter(Boolean);
11307
- if (segments.length !== 4) return null;
11308
- if (segments[0] !== "uploads") return null;
11412
+ if (segments.length !== 5) return null;
11413
+ if (segments[0] !== "accounts" || segments[2] !== "uploads") return null;
11309
11414
  const accountId = segments[1];
11310
- const attachmentId = segments[2];
11311
- const filename = segments[3];
11415
+ const attachmentId = segments[3];
11416
+ const filename = segments[4];
11312
11417
  if (!UUID_RE2.test(accountId) || !UUID_RE2.test(attachmentId)) return null;
11313
11418
  const dot = filename.lastIndexOf(".");
11314
11419
  if (dot === -1) return null;
@@ -11386,6 +11491,17 @@ import { resolve as resolve20, relative as relative2, join as join18, basename a
11386
11491
  import { tmpdir as tmpdir2 } from "os";
11387
11492
  import { execFile as execFile2 } from "child_process";
11388
11493
  import { promisify as promisify2 } from "util";
11494
+
11495
+ // app/lib/migration-state.ts
11496
+ var inProgress = false;
11497
+ function setUploadsMigrationInProgress(value) {
11498
+ inProgress = value;
11499
+ }
11500
+ function uploadsMigrationInProgress() {
11501
+ return inProgress;
11502
+ }
11503
+
11504
+ // app/lib/file-index.ts
11389
11505
  var execFileAsync2 = promisify2(execFile2);
11390
11506
  var CONTENT_CAP = 1e5;
11391
11507
  var OCR_TIMEOUT_MS = 3e4;
@@ -11690,7 +11806,6 @@ async function reconcileFileIndex(accountId, opts) {
11690
11806
  return withSession(opts, async (session) => {
11691
11807
  const walked = [];
11692
11808
  const subtreeRoots = [
11693
- resolve20(dataRoot, "uploads", accountId),
11694
11809
  resolve20(dataRoot, "accounts", accountId)
11695
11810
  ];
11696
11811
  let clean = true;
@@ -11727,7 +11842,9 @@ async function reconcileFileIndex(accountId, opts) {
11727
11842
  }
11728
11843
  let pruned = 0;
11729
11844
  let cascadeKds = 0, cascadeSections = 0, cascadeChunks = 0;
11730
- if (clean) {
11845
+ if (clean && uploadsMigrationInProgress()) {
11846
+ console.error(`[file-index] reconcile account=${accountId} prune-skipped reason="uploads-migration-in-progress"`);
11847
+ } else if (clean) {
11731
11848
  const foundPaths = walked.map((w) => w.relativePath);
11732
11849
  const goneRes = await session.run(
11733
11850
  `MATCH (f:FileArtifact {accountId: $accountId})
@@ -11958,7 +12075,7 @@ app19.get("/", requireAdminSession, async (c) => {
11958
12075
  }
11959
12076
  const names = await readdir3(absolute);
11960
12077
  const segs = relPath.split("/").filter(Boolean);
11961
- const isAccountParent = segs.length === 1 && (segs[0] === "accounts" || segs[0] === "uploads");
12078
+ const isAccountParent = segs.length === 1 && segs[0] === "accounts";
11962
12079
  const entries = [];
11963
12080
  for (const name of names) {
11964
12081
  if (UUID_RE3.test(name.replace(/\.meta\.json$/, "")) && name.endsWith(".meta.json")) {
@@ -11967,6 +12084,8 @@ app19.get("/", requireAdminSession, async (c) => {
11967
12084
  if (isAccountParent && UUID_RE3.test(name) && name !== accountId) {
11968
12085
  continue;
11969
12086
  }
12087
+ const childRel = relPath === "" || relPath === "." ? name : `${relPath}/${name}`;
12088
+ const protectedEntry = isProtectedFromDeletion(childRel).protected;
11970
12089
  try {
11971
12090
  const entryPath = join19(absolute, name);
11972
12091
  const s = await stat4(entryPath);
@@ -11974,10 +12093,11 @@ app19.get("/", requireAdminSession, async (c) => {
11974
12093
  name,
11975
12094
  kind: s.isDirectory() ? "directory" : s.isFile() ? "file" : "other",
11976
12095
  sizeBytes: s.isDirectory() ? null : s.size,
11977
- modifiedAt: s.mtime.toISOString()
12096
+ modifiedAt: s.mtime.toISOString(),
12097
+ protected: protectedEntry
11978
12098
  });
11979
12099
  } catch {
11980
- entries.push({ name, kind: "other", sizeBytes: null, modifiedAt: (/* @__PURE__ */ new Date(0)).toISOString() });
12100
+ entries.push({ name, kind: "other", sizeBytes: null, modifiedAt: (/* @__PURE__ */ new Date(0)).toISOString(), protected: protectedEntry });
11981
12101
  }
11982
12102
  }
11983
12103
  const accountNames = await readAccountNames();
@@ -12093,14 +12213,14 @@ app19.post("/upload", requireAdminSession, async (c) => {
12093
12213
  }
12094
12214
  const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
12095
12215
  const finalName = `${Date.now()}-${safeName}`;
12096
- const destDir = resolve21(DATA_ROOT, "uploads", accountId);
12216
+ const destDir = resolve21(DATA_ROOT, "accounts", accountId, "uploads");
12097
12217
  const destPath = resolve21(destDir, finalName);
12098
12218
  try {
12099
12219
  await mkdir3(destDir, { recursive: true });
12100
12220
  const dataRootReal = realpathSync4(DATA_ROOT);
12101
12221
  const destDirReal = realpathSync4(destDir);
12102
12222
  if (destDirReal !== dataRootReal && !destDirReal.startsWith(dataRootReal + sep5)) {
12103
- console.error(`[data] path-traversal-blocked requested="uploads/${accountId}" resolved="${destDirReal}"`);
12223
+ console.error(`[data] path-traversal-blocked requested="accounts/${accountId}/uploads" resolved="${destDirReal}"`);
12104
12224
  return c.json({ error: "Upload destination escapes DATA_ROOT" }, 403);
12105
12225
  }
12106
12226
  const buffer = Buffer.from(await file.arrayBuffer());
@@ -12110,7 +12230,7 @@ app19.post("/upload", requireAdminSession, async (c) => {
12110
12230
  console.error(`[data] file-upload error filename="${safeName}" err="${message}"`);
12111
12231
  return c.json({ error: message }, 500);
12112
12232
  }
12113
- const relativeDest = `uploads/${accountId}/${finalName}`;
12233
+ const relativeDest = `accounts/${accountId}/uploads/${finalName}`;
12114
12234
  console.error(`[data] file-upload filename="${safeName}" size=${file.size} dest="${relativeDest}"`);
12115
12235
  void indexFile(accountId, relativeDest).catch((err) => {
12116
12236
  console.error(`[data] file-upload index-failed dest="${relativeDest}" err="${err instanceof Error ? err.message : String(err)}"`);
@@ -14245,13 +14365,14 @@ async function fetchAccountFileArtefacts(accountId) {
14245
14365
  const result = await session.run(
14246
14366
  `MATCH (f:FileArtifact { accountId: $accountId })
14247
14367
  WHERE f.relativePath STARTS WITH 'accounts/'
14368
+ AND NOT f.relativePath STARTS WITH $uploadsPrefix
14248
14369
  RETURN f.relativePath AS relativePath,
14249
14370
  f.displayName AS displayName,
14250
14371
  f.mimeType AS mimeType,
14251
14372
  f.modifiedAt AS modifiedAt
14252
14373
  ORDER BY f.modifiedAt DESC
14253
14374
  LIMIT 50`,
14254
- { accountId }
14375
+ { accountId, uploadsPrefix: `accounts/${accountId}/uploads/` }
14255
14376
  );
14256
14377
  return result.records.map((r) => {
14257
14378
  const relativePath = r.get("relativePath");
@@ -14507,7 +14628,7 @@ var session_archive_default = app29;
14507
14628
 
14508
14629
  // server/routes/admin/session-rc-spawn.ts
14509
14630
  import { randomUUID as randomUUID10 } from "crypto";
14510
- function resolveSpawnPlan(host, operatorDomains, body, mintId = randomUUID10) {
14631
+ function resolveSpawnPlan(host, operatorDomains, body, mintId = randomUUID10, adminUserId) {
14511
14632
  const operator = isOperatorHost(host, operatorDomains);
14512
14633
  const resumeId = typeof body.sessionId === "string" && body.sessionId.length > 0 ? body.sessionId : void 0;
14513
14634
  const name = typeof body.name === "string" && body.name.length > 0 ? body.name : void 0;
@@ -14525,6 +14646,7 @@ function resolveSpawnPlan(host, operatorDomains, body, mintId = randomUUID10) {
14525
14646
  payload.senderId = key;
14526
14647
  payload.webchatChannel = webchatChannelDescriptor(key);
14527
14648
  if (name) payload.name = name;
14649
+ if (adminUserId) payload.adminUserId = adminUserId;
14528
14650
  } else {
14529
14651
  sessionId = resumeId;
14530
14652
  payload.sessionId = resumeId;
@@ -14545,7 +14667,9 @@ app30.post("/", requireAdminSession, async (c) => {
14545
14667
  body = {};
14546
14668
  }
14547
14669
  const host = (c.req.header("host") ?? "").split(":")[0];
14548
- const plan = resolveSpawnPlan(host, getOperatorDomains(), body);
14670
+ const cacheKey = c.get("cacheKey");
14671
+ const adminUserId = cacheKey ? getUserIdForSession(cacheKey) : void 0;
14672
+ const plan = resolveSpawnPlan(host, getOperatorDomains(), body, randomUUID10, adminUserId);
14549
14673
  const spawnReqId = randomUUID10();
14550
14674
  console.log(
14551
14675
  `[chat-spawn] op=request spawnReqId=${spawnReqId} origin=${plan.origin} kind=${plan.kind} sessionId=${plan.kind === "new" ? "new" : plan.sessionId}`
@@ -14714,9 +14838,9 @@ var system_stats_default = app31;
14714
14838
  // server/routes/admin/health.ts
14715
14839
  import { existsSync as existsSync22, readFileSync as readFileSync21 } from "fs";
14716
14840
  import { resolve as resolve23, join as join20 } from "path";
14717
- var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve23(process.cwd(), "..");
14841
+ var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve23(process.cwd(), "..");
14718
14842
  var brandHostname = "maxy";
14719
- var brandJsonPath = join20(PLATFORM_ROOT7, "config", "brand.json");
14843
+ var brandJsonPath = join20(PLATFORM_ROOT6, "config", "brand.json");
14720
14844
  if (existsSync22(brandJsonPath)) {
14721
14845
  try {
14722
14846
  const brand = JSON.parse(readFileSync21(brandJsonPath, "utf-8"));
@@ -14724,7 +14848,7 @@ if (existsSync22(brandJsonPath)) {
14724
14848
  } catch {
14725
14849
  }
14726
14850
  }
14727
- var VERSION_FILE = resolve23(PLATFORM_ROOT7, `config/.${brandHostname}-version`);
14851
+ var VERSION_FILE = resolve23(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
14728
14852
  var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
14729
14853
  var PROBE_TIMEOUT_MS = 1e3;
14730
14854
  function readVersion() {
@@ -15434,9 +15558,9 @@ var verify_token_default = app41;
15434
15558
  // app/lib/access-email.ts
15435
15559
  import { spawn as spawn2 } from "child_process";
15436
15560
  import { resolve as resolve24 } from "path";
15437
- var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
15561
+ var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
15438
15562
  var SEND_SCRIPT = resolve24(
15439
- PLATFORM_ROOT8,
15563
+ PLATFORM_ROOT7,
15440
15564
  "plugins",
15441
15565
  "email",
15442
15566
  "mcp",
@@ -16672,7 +16796,7 @@ import * as fsp2 from "fs/promises";
16672
16796
  import { resolve as resolve27, sep as sep7 } from "path";
16673
16797
  var ACCOUNT_UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
16674
16798
  var DEFAULT_COALESCE_MS = 500;
16675
- var ROOTS = ["uploads", "accounts"];
16799
+ var ROOTS = ["accounts"];
16676
16800
  function _routeEvent(rootName, filename) {
16677
16801
  if (!filename) return null;
16678
16802
  const segs = filename.split(sep7).filter(Boolean);
@@ -16770,6 +16894,129 @@ async function startFileWatcher(opts = {}) {
16770
16894
  return { stop };
16771
16895
  }
16772
16896
 
16897
+ // app/lib/migrate-uploads.ts
16898
+ import { mkdir as mkdir5, readdir as readdir5, rename, rm as rm3 } from "fs/promises";
16899
+ import { dirname as dirname7, relative as relative4, resolve as resolve28 } from "path";
16900
+ var ACCOUNT_UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
16901
+ async function walkFiles(dir) {
16902
+ const out = [];
16903
+ let entries;
16904
+ try {
16905
+ entries = await readdir5(dir, { withFileTypes: true });
16906
+ } catch {
16907
+ return out;
16908
+ }
16909
+ for (const e of entries) {
16910
+ const abs = resolve28(dir, e.name);
16911
+ if (e.isDirectory()) {
16912
+ out.push(...await walkFiles(abs));
16913
+ } else if (e.isFile()) {
16914
+ out.push(abs);
16915
+ }
16916
+ }
16917
+ return out;
16918
+ }
16919
+ function toNumber2(v) {
16920
+ if (v && typeof v.toNumber === "function") {
16921
+ return v.toNumber();
16922
+ }
16923
+ return Number(v ?? 0);
16924
+ }
16925
+ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
16926
+ const files = await walkFiles(srcDir);
16927
+ let moved = 0;
16928
+ for (const oldAbs of files) {
16929
+ const suffix = relative4(srcDir, oldAbs);
16930
+ const newAbs = resolve28(destDir, suffix);
16931
+ await mkdir5(dirname7(newAbs), { recursive: true });
16932
+ await rename(oldAbs, newAbs);
16933
+ moved++;
16934
+ const oldRel = relative4(dataRoot, oldAbs);
16935
+ const newRel = relative4(dataRoot, newAbs);
16936
+ let linked = false;
16937
+ if (accountId && session) {
16938
+ const fa = await session.run(
16939
+ `MATCH (f:FileArtifact {accountId: $accountId, relativePath: $oldRel})
16940
+ SET f.relativePath = $newRel RETURN count(f) AS n`,
16941
+ { accountId, oldRel, newRel }
16942
+ );
16943
+ const kd = await session.run(
16944
+ `MATCH (d:KnowledgeDocument {accountId: $accountId, sourcePath: $oldAbs})
16945
+ SET d.sourcePath = $newAbs RETURN count(d) AS n`,
16946
+ { accountId, oldAbs, newAbs }
16947
+ );
16948
+ linked = toNumber2(fa.records[0]?.get("n")) + toNumber2(kd.records[0]?.get("n")) > 0;
16949
+ }
16950
+ console.error(`[uploads-migrate] from=${oldRel} to=${newRel} linked=${linked}`);
16951
+ }
16952
+ await rm3(srcDir, { recursive: true, force: true });
16953
+ return moved;
16954
+ }
16955
+ async function migrateUploads(opts = {}) {
16956
+ const dataRoot = opts.dataRoot ?? DATA_ROOT;
16957
+ const oldRoot = resolve28(dataRoot, "uploads");
16958
+ let topEntries;
16959
+ try {
16960
+ topEntries = await readdir5(oldRoot, { withFileTypes: true });
16961
+ } catch (err) {
16962
+ if (err.code === "ENOENT") {
16963
+ return { moved: 0, skipped: 0, removedRoot: false };
16964
+ }
16965
+ throw err;
16966
+ }
16967
+ const installAccountId = opts.installAccountId ?? resolveAccount()?.accountId;
16968
+ const session = opts.session ?? getSession();
16969
+ const ownSession = !opts.session;
16970
+ setUploadsMigrationInProgress(true);
16971
+ let moved = 0;
16972
+ let skipped = 0;
16973
+ try {
16974
+ for (const entry of topEntries) {
16975
+ const name = entry.name;
16976
+ if (ACCOUNT_UUID_RE3.test(name)) {
16977
+ moved += await relocateTree(
16978
+ resolve28(oldRoot, name),
16979
+ resolve28(dataRoot, "accounts", name, "uploads"),
16980
+ dataRoot,
16981
+ name,
16982
+ session
16983
+ );
16984
+ } else if (name === "public") {
16985
+ if (!installAccountId) {
16986
+ console.error(`[uploads-migrate] skip entry="public" reason="no-install-account"`);
16987
+ skipped++;
16988
+ continue;
16989
+ }
16990
+ moved += await relocateTree(
16991
+ resolve28(oldRoot, "public"),
16992
+ resolve28(dataRoot, "accounts", installAccountId, "uploads", "public"),
16993
+ dataRoot,
16994
+ null,
16995
+ // public uploads carry no graph nodes
16996
+ session
16997
+ );
16998
+ } else {
16999
+ console.error(`[uploads-migrate] skip entry="${name}" reason="not-account-or-public"`);
17000
+ skipped++;
17001
+ }
17002
+ }
17003
+ let removedRoot = false;
17004
+ try {
17005
+ const remaining = await readdir5(oldRoot);
17006
+ if (remaining.length === 0) {
17007
+ await rm3(oldRoot, { recursive: true, force: true });
17008
+ removedRoot = true;
17009
+ }
17010
+ } catch {
17011
+ }
17012
+ console.error(`[uploads-migrate] done moved=${moved} skipped=${skipped} removedRoot=${removedRoot}`);
17013
+ return { moved, skipped, removedRoot };
17014
+ } finally {
17015
+ setUploadsMigrationInProgress(false);
17016
+ if (ownSession) await session.close();
17017
+ }
17018
+ }
17019
+
16773
17020
  // app/lib/whatsapp/gateway/inbound-hub.ts
16774
17021
  var InboundHub = class {
16775
17022
  senders = /* @__PURE__ */ new Map();
@@ -17681,7 +17928,7 @@ function broadcastAdminShutdown(reason) {
17681
17928
  // ../lib/entitlement/src/index.ts
17682
17929
  import { createPublicKey, createHash as createHash5, verify as cryptoVerify } from "crypto";
17683
17930
  import { existsSync as existsSync26, readFileSync as readFileSync25, statSync as statSync10 } from "fs";
17684
- import { resolve as resolve28 } from "path";
17931
+ import { resolve as resolve29 } from "path";
17685
17932
 
17686
17933
  // ../lib/entitlement/src/canonicalize.ts
17687
17934
  function canonicalize(value) {
@@ -17716,7 +17963,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
17716
17963
  var GRACE_DAYS = 7;
17717
17964
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
17718
17965
  function pubkeyPath(brand) {
17719
- return resolve28(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
17966
+ return resolve29(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
17720
17967
  }
17721
17968
  var memo = null;
17722
17969
  function memoKey(mtimeMs, account) {
@@ -17728,7 +17975,7 @@ function resolveEntitlement(brand, account) {
17728
17975
  if (brand.commercialMode !== true) {
17729
17976
  return logResolved(implicitTrust(account), null);
17730
17977
  }
17731
- const entitlementPath = resolve28(brand.configDir, "entitlement.json");
17978
+ const entitlementPath = resolve29(brand.configDir, "entitlement.json");
17732
17979
  if (!existsSync26(entitlementPath)) {
17733
17980
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
17734
17981
  }
@@ -17918,8 +18165,8 @@ function clientFrom(c) {
17918
18165
  c.req.header("x-forwarded-for")
17919
18166
  );
17920
18167
  }
17921
- var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
17922
- var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join22(PLATFORM_ROOT9, "config", "brand.json") : "";
18168
+ var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
18169
+ var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join22(PLATFORM_ROOT8, "config", "brand.json") : "";
17923
18170
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
17924
18171
  if (BRAND_JSON_PATH && !existsSync27(BRAND_JSON_PATH)) {
17925
18172
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
@@ -17977,7 +18224,7 @@ var app49 = new Hono();
17977
18224
  var nativeFileFollowers = /* @__PURE__ */ new Map();
17978
18225
  var waGateway = new WaGateway({
17979
18226
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
17980
- serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve29(process.env.MAXY_PLATFORM_ROOT ?? join22(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
18227
+ serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve30(process.env.MAXY_PLATFORM_ROOT ?? join22(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
17981
18228
  // Task 751 — file delivery on the native channel: resolve the platform
17982
18229
  // account + path validation here and funnel through the shared send core.
17983
18230
  sendDocument: async ({ senderId, accountId, filePath, caption }) => {
@@ -17993,7 +18240,7 @@ var waGateway = new WaGateway({
17993
18240
  caption,
17994
18241
  accountId,
17995
18242
  maxyAccountId,
17996
- platformRoot: resolve29(process.env.MAXY_PLATFORM_ROOT ?? join22(__dirname, ".."))
18243
+ platformRoot: resolve30(process.env.MAXY_PLATFORM_ROOT ?? join22(__dirname, ".."))
17997
18244
  });
17998
18245
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
17999
18246
  },
@@ -18376,8 +18623,8 @@ app49.get("/agent-assets/:slug/:filename", (c) => {
18376
18623
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
18377
18624
  return c.text("Not found", 404);
18378
18625
  }
18379
- const filePath = resolve29(account.accountDir, "agents", slug, "assets", filename);
18380
- const expectedDir = resolve29(account.accountDir, "agents", slug, "assets");
18626
+ const filePath = resolve30(account.accountDir, "agents", slug, "assets", filename);
18627
+ const expectedDir = resolve30(account.accountDir, "agents", slug, "assets");
18381
18628
  if (!filePath.startsWith(expectedDir + "/")) {
18382
18629
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
18383
18630
  return c.text("Forbidden", 403);
@@ -18406,8 +18653,8 @@ app49.get("/generated/:filename", (c) => {
18406
18653
  console.error(`[generated] serve file=${filename} status=404`);
18407
18654
  return c.text("Not found", 404);
18408
18655
  }
18409
- const filePath = resolve29(account.accountDir, "generated", filename);
18410
- const expectedDir = resolve29(account.accountDir, "generated");
18656
+ const filePath = resolve30(account.accountDir, "generated", filename);
18657
+ const expectedDir = resolve30(account.accountDir, "generated");
18411
18658
  if (!filePath.startsWith(expectedDir + "/")) {
18412
18659
  console.error(`[generated] serve file=${filename} status=403`);
18413
18660
  return c.text("Forbidden", 403);
@@ -18450,19 +18697,19 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
18450
18697
  })}</script>`;
18451
18698
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
18452
18699
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
18453
- var brandIconFsPath = resolve29(process.cwd(), "public", brandIconPath.replace(/^\//, ""));
18700
+ var brandIconFsPath = resolve30(process.cwd(), "public", brandIconPath.replace(/^\//, ""));
18454
18701
  var brandIconExists = existsSync27(brandIconFsPath);
18455
18702
  var SW_SOURCE = (() => {
18456
18703
  try {
18457
- return readFileSync26(resolve29(process.cwd(), "public", "sw.js"), "utf-8");
18704
+ return readFileSync26(resolve30(process.cwd(), "public", "sw.js"), "utf-8");
18458
18705
  } catch {
18459
18706
  return null;
18460
18707
  }
18461
18708
  })();
18462
18709
  function readInstalledVersion() {
18463
18710
  try {
18464
- if (!PLATFORM_ROOT9) return "unknown";
18465
- const versionFile = join22(PLATFORM_ROOT9, "config", `.${BRAND.hostname}-version`);
18711
+ if (!PLATFORM_ROOT8) return "unknown";
18712
+ const versionFile = join22(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
18466
18713
  if (!existsSync27(versionFile)) return "unknown";
18467
18714
  const content = readFileSync26(versionFile, "utf-8").trim();
18468
18715
  return content || "unknown";
@@ -18505,7 +18752,7 @@ var clientErrorReporterScript = `<script>
18505
18752
  function cachedHtml(file) {
18506
18753
  let html = htmlCache.get(file);
18507
18754
  if (!html) {
18508
- html = readFileSync26(resolve29(process.cwd(), "public", file), "utf-8");
18755
+ html = readFileSync26(resolve30(process.cwd(), "public", file), "utf-8");
18509
18756
  const productNameEsc = escapeHtml(BRAND.productName);
18510
18757
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
18511
18758
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -18623,7 +18870,7 @@ app49.use("/vnc-popout.html", logViewerFetch);
18623
18870
  app49.get("/vnc-popout.html", (c) => {
18624
18871
  let html = htmlCache.get("vnc-popout.html");
18625
18872
  if (!html) {
18626
- html = readFileSync26(resolve29(process.cwd(), "public", "vnc-popout.html"), "utf-8");
18873
+ html = readFileSync26(resolve30(process.cwd(), "public", "vnc-popout.html"), "utf-8");
18627
18874
  const name = escapeHtml(BRAND.productName);
18628
18875
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
18629
18876
  html = html.replace("</head>", ` ${brandScript}
@@ -18849,8 +19096,10 @@ var adminUserReconcileTimer = setInterval(() => {
18849
19096
  }, ADMINUSER_RECONCILE_INTERVAL_MS);
18850
19097
  if (typeof adminUserReconcileTimer.unref === "function") adminUserReconcileTimer.unref();
18851
19098
  startGraphHealthTimer();
18852
- void startFileWatcher().catch((err) => {
18853
- console.error(`[file-watcher] start-failed err="${err instanceof Error ? err.message : String(err)}"`);
19099
+ void migrateUploads().catch((err) => console.error(`[uploads-migrate] failed err="${err instanceof Error ? err.message : String(err)}"`)).finally(() => {
19100
+ void startFileWatcher().catch((err) => {
19101
+ console.error(`[file-watcher] start-failed err="${err instanceof Error ? err.message : String(err)}"`);
19102
+ });
18854
19103
  });
18855
19104
  try {
18856
19105
  const accounts = enumerateValidAccountIds(getAccountsDirFromEnv());
@@ -18932,7 +19181,7 @@ if (bootAccountConfig?.whatsapp) {
18932
19181
  }
18933
19182
  init({
18934
19183
  configDir: configDirForWhatsApp,
18935
- platformRoot: resolve29(process.env.MAXY_PLATFORM_ROOT ?? join22(__dirname, "..")),
19184
+ platformRoot: resolve30(process.env.MAXY_PLATFORM_ROOT ?? join22(__dirname, "..")),
18936
19185
  accountConfig: bootAccountConfig,
18937
19186
  onMessage: async (msg) => {
18938
19187
  if (msg.isOwnerMirror) {