@rubytech/create-maxy-code 0.1.478 → 0.1.480

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 (252) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +1244 -0
  3. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +462 -0
  4. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +321 -0
  5. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +830 -0
  6. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +2177 -0
  7. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +201 -0
  8. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +217 -0
  9. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +80 -0
  10. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +163 -0
  11. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +163 -0
  12. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
  13. package/payload/platform/lib/graph-style/dist/index.js +22 -0
  14. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  15. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +6 -0
  16. package/payload/platform/lib/graph-style/src/index.ts +20 -0
  17. package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +155 -12
  18. package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
  19. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +3 -0
  20. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
  21. package/payload/platform/lib/storage-broker/dist/cf-exec.js +68 -28
  22. package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
  23. package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +194 -16
  24. package/payload/platform/lib/storage-broker/src/cf-exec.ts +74 -29
  25. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +13 -1
  26. package/payload/platform/plugins/cloudflare/PLUGIN.md +26 -1
  27. package/payload/platform/plugins/cloudflare/bin/d1-http.mjs +80 -0
  28. package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +17 -2
  29. package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +357 -0
  30. package/payload/platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs +103 -0
  31. package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +2 -2
  32. package/payload/platform/plugins/cloudflare/mcp/__tests__/d1-http.test.ts +96 -0
  33. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +120 -0
  34. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +59 -4
  35. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +176 -0
  36. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-state-contract.test.ts +75 -0
  37. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +131 -0
  38. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +68 -0
  39. package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +88 -0
  40. package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +39 -0
  41. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +68 -1
  42. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +30 -1
  43. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +87 -0
  44. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +8 -4
  45. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +10 -0
  46. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +107 -1
  47. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +82 -2
  48. package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -0
  49. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +56 -0
  50. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +98 -2
  51. package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +12 -0
  52. package/payload/platform/plugins/docs/references/cloudflare.md +12 -0
  53. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +41 -1
  54. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
  55. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +249 -2
  56. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -1
  57. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +127 -3
  58. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -1
  59. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +17 -0
  60. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
  61. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +26 -1
  62. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
  63. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +37 -1
  64. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -1
  65. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +126 -5
  66. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -1
  67. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +18 -0
  68. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -1
  69. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +123 -18
  70. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
  71. package/payload/platform/plugins/outlook/references/auth.md +45 -2
  72. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts +2 -0
  73. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts.map +1 -0
  74. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js +66 -0
  75. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js.map +1 -0
  76. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts +41 -0
  77. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts.map +1 -0
  78. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js +75 -0
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js.map +1 -0
  80. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +122 -0
  81. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  82. package/payload/server/{chunk-3XLLTG6R.js → chunk-REWXLAPC.js} +30 -10
  83. package/payload/server/package.json +1 -1
  84. package/payload/server/public/activity.html +7 -6
  85. package/payload/server/public/assets/{AdminLoginScreens-DR5eLF-a.js → AdminLoginScreens-DXYJWXaX.js} +1 -1
  86. package/payload/server/public/assets/AdminShell-UDgNN4CA.js +2 -0
  87. package/payload/server/public/assets/Checkbox-C08Ulc_a.js +1 -0
  88. package/payload/server/public/assets/activity-06gsRDVv.js +1 -0
  89. package/payload/server/public/assets/admin-BhiHyORH.js +1 -0
  90. package/payload/server/public/assets/{arc-BrHWN2Kr.js → arc-BHXuzTwg.js} +1 -1
  91. package/payload/server/public/assets/architecture-YZFGNWBL-Ct5q6ks1.js +1 -0
  92. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Clt9NOr0.js → architectureDiagram-Q4EWVU46-DsN_vxau.js} +1 -1
  93. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BOe_jmXi.js → blockDiagram-DXYQGD6D-CvNat1uB.js} +1 -1
  94. package/payload/server/public/assets/browser-Cz4IKS47.js +1 -0
  95. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-B64FaKMV.js → c4Diagram-AHTNJAMY-E9yqTPTW.js} +1 -1
  96. package/payload/server/public/assets/calendar-D0xtNqH8.js +1 -0
  97. package/payload/server/public/assets/channel-PE9vBKEQ.js +1 -0
  98. package/payload/server/public/assets/chat-DwLJ830n.js +1 -0
  99. package/payload/server/public/assets/chevron-left-Cv-TFuEf.js +1 -0
  100. package/payload/server/public/assets/{chunk-2KRD3SAO-BGAUfWvM.js → chunk-2KRD3SAO-DpzI_0EY.js} +1 -1
  101. package/payload/server/public/assets/{chunk-336JU56O-PFi6uQZ0.js → chunk-336JU56O-eR_I9Eu4.js} +2 -2
  102. package/payload/server/public/assets/chunk-426QAEUC-BDNZtJsz.js +1 -0
  103. package/payload/server/public/assets/{chunk-4BX2VUAB-CkxXXCiF.js → chunk-4BX2VUAB-D-6xk1t8.js} +1 -1
  104. package/payload/server/public/assets/{chunk-4TB4RGXK-B-7tN4IA.js → chunk-4TB4RGXK-BkqUcFpL.js} +1 -1
  105. package/payload/server/public/assets/{chunk-55IACEB6-CBkNmqrt.js → chunk-55IACEB6-ChCl2pSP.js} +1 -1
  106. package/payload/server/public/assets/{chunk-5FUZZQ4R-wa9ujn7i.js → chunk-5FUZZQ4R-DasGsg54.js} +1 -1
  107. package/payload/server/public/assets/{chunk-5PVQY5BW-DIk-xBaE.js → chunk-5PVQY5BW-DXlHUX1h.js} +1 -1
  108. package/payload/server/public/assets/{chunk-67CJDMHE-BbMV1lgW.js → chunk-67CJDMHE-BZfVpTsE.js} +1 -1
  109. package/payload/server/public/assets/{chunk-7N4EOEYR-C49Mv-nQ.js → chunk-7N4EOEYR-C6U5TIgT.js} +1 -1
  110. package/payload/server/public/assets/{chunk-AA7GKIK3-Dy_pJOp_.js → chunk-AA7GKIK3-CA7cVDmw.js} +1 -1
  111. package/payload/server/public/assets/{chunk-BSJP7CBP-Ce-OmPqn.js → chunk-BSJP7CBP-DxN2GC3p.js} +1 -1
  112. package/payload/server/public/assets/{chunk-CIAEETIT-tnjq-APC.js → chunk-CIAEETIT-C0-4wA8y.js} +1 -1
  113. package/payload/server/public/assets/{chunk-EDXVE4YY-Bvv_deZ2.js → chunk-EDXVE4YY--kyb1rLc.js} +1 -1
  114. package/payload/server/public/assets/{chunk-ENJZ2VHE-CmxCPKyU.js → chunk-ENJZ2VHE-FBN6hR7V.js} +1 -1
  115. package/payload/server/public/assets/{chunk-FMBD7UC4-Top6y08S.js → chunk-FMBD7UC4-Lpge4M5g.js} +1 -1
  116. package/payload/server/public/assets/{chunk-FOC6F5B3-B9LPNCUS.js → chunk-FOC6F5B3-CM1rwOou.js} +1 -1
  117. package/payload/server/public/assets/{chunk-ICPOFSXX-Bi-_c351.js → chunk-ICPOFSXX-JdYLY3yu.js} +2 -2
  118. package/payload/server/public/assets/{chunk-K5T4RW27-DAnRM04h.js → chunk-K5T4RW27-DCUblGBr.js} +1 -1
  119. package/payload/server/public/assets/{chunk-KGLVRYIC-C0QYcbLe.js → chunk-KGLVRYIC-BP_cide2.js} +1 -1
  120. package/payload/server/public/assets/{chunk-LIHQZDEY-BjvA5ykt.js → chunk-LIHQZDEY-DjMa1lOl.js} +1 -1
  121. package/payload/server/public/assets/{chunk-ORNJ4GCN-Cs46Gj7z.js → chunk-ORNJ4GCN-D51eJAoV.js} +1 -1
  122. package/payload/server/public/assets/{chunk-OYMX7WX6-BBpimab5.js → chunk-OYMX7WX6-4hWIO544.js} +1 -1
  123. package/payload/server/public/assets/chunk-QZHKN3VN-B_Hris8x.js +1 -0
  124. package/payload/server/public/assets/{chunk-U2HBQHQK-CQUSBHoK.js → chunk-U2HBQHQK-BwdQf9cS.js} +1 -1
  125. package/payload/server/public/assets/{chunk-X2U36JSP-Dm8TevG0.js → chunk-X2U36JSP-DmDxwLOg.js} +1 -1
  126. package/payload/server/public/assets/{chunk-XPW4576I-B10QuxYb.js → chunk-XPW4576I-JQyAB7ND.js} +1 -1
  127. package/payload/server/public/assets/{chunk-YZCP3GAM-vaGsu4Wk.js → chunk-YZCP3GAM-BonLdCzI.js} +1 -1
  128. package/payload/server/public/assets/{chunk-ZZ45TVLE-D1Qi22bL.js → chunk-ZZ45TVLE-BXKB2wSk.js} +1 -1
  129. package/payload/server/public/assets/classDiagram-6PBFFD2Q-D8Q05iMI.js +1 -0
  130. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Dm3hAxfm.js +1 -0
  131. package/payload/server/public/assets/clone-C-wOwvpY.js +1 -0
  132. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-CRDvLRXs.js → cose-bilkent-S5V4N54A-BDnd4Blw.js} +1 -1
  133. package/payload/server/public/assets/{dagre-CEmqJTt3.js → dagre-BmexzuER.js} +1 -1
  134. package/payload/server/public/assets/{dagre-KV5264BT-LXMTo7Fp.js → dagre-KV5264BT-D4WVuwPj.js} +1 -1
  135. package/payload/server/public/assets/data-DUDJfnjI.js +1 -0
  136. package/payload/server/public/assets/{diagram-5BDNPKRD-zyis6nPt.js → diagram-5BDNPKRD-Bbf4mT8a.js} +1 -1
  137. package/payload/server/public/assets/{diagram-G4DWMVQ6-BiU0XAAD.js → diagram-G4DWMVQ6-BpqVTUmx.js} +1 -1
  138. package/payload/server/public/assets/{diagram-MMDJMWI5-rGFiz1SX.js → diagram-MMDJMWI5-DY-k5k6r.js} +1 -1
  139. package/payload/server/public/assets/{diagram-TYMM5635-B0wak1ZJ.js → diagram-TYMM5635-CXHxwacS.js} +1 -1
  140. package/payload/server/public/assets/{erDiagram-SMLLAGMA-D8AUxLyX.js → erDiagram-SMLLAGMA-BNKEc3l8.js} +1 -1
  141. package/payload/server/public/assets/{flatten-naJ5PJ4f.js → flatten-jn8bZgcI.js} +1 -1
  142. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BtdUf68b.js → flowDiagram-DWJPFMVM-CbAeuJsi.js} +1 -1
  143. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-C-lkzDmf.js → ganttDiagram-T4ZO3ILL-CeeNnL4l.js} +1 -1
  144. package/payload/server/public/assets/gitGraph-7Q5UKJZL-BBHsY16R.js +1 -0
  145. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-DRUKumq2.js → gitGraphDiagram-UUTBAWPF-DG8RCtfg.js} +1 -1
  146. package/payload/server/public/assets/{graph-DRloNic4.js → graph-BAoktBcx.js} +19 -19
  147. package/payload/server/public/assets/graph-labels-LeIhl7ZG.js +1 -0
  148. package/payload/server/public/assets/{graphlib-zRsaoFEC.js → graphlib-BkDExPQi.js} +1 -1
  149. package/payload/server/public/assets/info-OMHHGYJF-C1j5I_zx.js +1 -0
  150. package/payload/server/public/assets/infoDiagram-42DDH7IO-ZWkwHWEu.js +2 -0
  151. package/payload/server/public/assets/{isEmpty-Bnpg7b8f.js → isEmpty-Dkk6_L-B.js} +1 -1
  152. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BguMgoR8.js → ishikawaDiagram-UXIWVN3A-D_iMHV64.js} +1 -1
  153. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CXhyK1n-.js → journeyDiagram-VCZTEJTY-BvPRpulx.js} +1 -1
  154. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-C0UCWVx-.js → kanban-definition-6JOO6SKY-CkWaXWo2.js} +1 -1
  155. package/payload/server/public/assets/{line--uhS9YDE.js → line-BGQT9unx.js} +1 -1
  156. package/payload/server/public/assets/{linear-CAOWPodQ.js → linear-DzrYI8-8.js} +1 -1
  157. package/payload/server/public/assets/{maximize-2-CzDaBugs.js → maximize-2-2MgPSwlx.js} +1 -1
  158. package/payload/server/public/assets/{mermaid-parser.core-D6kPx7vc.js → mermaid-parser.core-DrMY7FMe.js} +2 -2
  159. package/payload/server/public/assets/{mermaid.core-CTd4DS6O.js → mermaid.core-BWdf6fZ7.js} +3 -3
  160. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-_AyvyG8C.js → mindmap-definition-QFDTVHPH-CgqM9h97.js} +1 -1
  161. package/payload/server/public/assets/operator-CZifmNhp.js +1 -0
  162. package/payload/server/public/assets/{ordinal-Wx4m9zHk.js → ordinal-DuJYM7p1.js} +1 -1
  163. package/payload/server/public/assets/packet-4T2RLAQJ-CGiTbe0N.js +1 -0
  164. package/payload/server/public/assets/{page-ef-6rRPL.js → page-B4kllGd_.js} +6 -6
  165. package/payload/server/public/assets/page-DbfE7QwJ.js +1 -0
  166. package/payload/server/public/assets/{pdf-render-CeX6d2v7.js → pdf-render-riHufgrn.js} +1 -1
  167. package/payload/server/public/assets/pie-ZZUOXDRM-BrK023dE.js +1 -0
  168. package/payload/server/public/assets/{pieDiagram-DEJITSTG-6TsUDkkQ.js → pieDiagram-DEJITSTG-BeUGoYS7.js} +1 -1
  169. package/payload/server/public/assets/public-VtmyYz8E.js +1 -0
  170. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CWw9gOI4.js → quadrantDiagram-34T5L4WZ-CX82WgKy.js} +1 -1
  171. package/payload/server/public/assets/radar-PYXPWWZC-p7NRamXX.js +1 -0
  172. package/payload/server/public/assets/{reduce-BUmc8RDR.js → reduce-COyotL2V.js} +1 -1
  173. package/payload/server/public/assets/{requirementDiagram-MS252O5E-DCOlxNvx.js → requirementDiagram-MS252O5E-CzqebQXj.js} +1 -1
  174. package/payload/server/public/assets/{rotate-ccw-DXEKUhXP.js → rotate-ccw-DaZdB2cz.js} +1 -1
  175. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-CW4Xbir3.js → sankeyDiagram-XADWPNL6-CaHi1yzU.js} +1 -1
  176. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-vhb_pBLG.js → sequenceDiagram-FGHM5R23-BF2AeiKW.js} +1 -1
  177. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-Cb1WExEX.js → stateDiagram-FHFEXIEX-DtUCA8Ih.js} +1 -1
  178. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-a4FvB3L8.js +1 -0
  179. package/payload/server/public/assets/tasks-CvqVDbfO.js +1 -0
  180. package/payload/server/public/assets/{time-entry-format-CsYAjplS.js → time-entry-format-TD-PPEIu.js} +1 -1
  181. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BAHTf_AF.js → timeline-definition-GMOUNBTQ-D3Q1YrKY.js} +1 -1
  182. package/payload/server/public/assets/treeView-SZITEDCU-CoWk8VoG.js +1 -0
  183. package/payload/server/public/assets/treemap-W4RFUUIX-BhWeGj8Y.js +1 -0
  184. package/payload/server/public/assets/{triangle-alert-0lSBIUg-.js → triangle-alert-BaLhVNnn.js} +1 -1
  185. package/payload/server/public/assets/useCopyFeedback-BhjPzRa3.js +1 -0
  186. package/payload/server/public/assets/useSubAccountSwitcher-CWRpVt1-.css +1 -0
  187. package/payload/server/public/assets/{useSubAccountSwitcher-BZnzc5C3.js → useSubAccountSwitcher-HEVLb6Z9.js} +2 -2
  188. package/payload/server/public/assets/useVoiceRecorder-CfGgj3vm.js +2 -0
  189. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DtEttvW4.js → vennDiagram-DHZGUBPP-DnjzQDGf.js} +1 -1
  190. package/payload/server/public/assets/wardley-RL74JXVD-C0q3Hqng.js +1 -0
  191. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-K06y6CVJ.js → wardleyDiagram-NUSXRM2D-T3WHrUvi.js} +1 -1
  192. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-D66c40lp.js → xychartDiagram-5P7HB3ND-2Efyv4h2.js} +1 -1
  193. package/payload/server/public/brand-defaults.css +1 -0
  194. package/payload/server/public/browser.html +4 -5
  195. package/payload/server/public/calendar.html +5 -6
  196. package/payload/server/public/chat.html +12 -13
  197. package/payload/server/public/data.html +11 -12
  198. package/payload/server/public/graph.html +10 -11
  199. package/payload/server/public/index.html +14 -15
  200. package/payload/server/public/operator.html +14 -15
  201. package/payload/server/public/privacy.html +1 -0
  202. package/payload/server/public/public.html +12 -13
  203. package/payload/server/public/tasks.html +7 -5
  204. package/payload/server/public/vnc-popout.html +1 -0
  205. package/payload/server/server.js +833 -389
  206. package/payload/server/{src-XQ63FPRE.js → src-JJ6PHO5I.js} +1 -1
  207. package/payload/server/public/assets/AdminShell-D9zaUxPd.js +0 -2
  208. package/payload/server/public/assets/Checkbox-DcbbAIe5.js +0 -1
  209. package/payload/server/public/assets/activity-C9P5NjEZ.js +0 -1
  210. package/payload/server/public/assets/admin-D2KLZnQW.js +0 -1
  211. package/payload/server/public/assets/architecture-YZFGNWBL-CZQzGRSM.js +0 -1
  212. package/payload/server/public/assets/browser-CDWqEiCD.js +0 -1
  213. package/payload/server/public/assets/calendar-BG7fOpbB.js +0 -1
  214. package/payload/server/public/assets/channel-zDUAN9Ks.js +0 -1
  215. package/payload/server/public/assets/chat-RyH-WYQC.js +0 -1
  216. package/payload/server/public/assets/chevron-left-DmXCr6nB.js +0 -1
  217. package/payload/server/public/assets/chunk-426QAEUC-iJnqNRug.js +0 -1
  218. package/payload/server/public/assets/chunk-QZHKN3VN-Qlc2PFAj.js +0 -1
  219. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CFsKL52p.js +0 -1
  220. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BWBUUCiO.js +0 -1
  221. package/payload/server/public/assets/clone-h5XhkDUL.js +0 -1
  222. package/payload/server/public/assets/data-BaY_LkLR.js +0 -1
  223. package/payload/server/public/assets/gitGraph-7Q5UKJZL-CBZPQJLj.js +0 -1
  224. package/payload/server/public/assets/graph-labels-DD-GqS4v.js +0 -1
  225. package/payload/server/public/assets/info-OMHHGYJF-1vIoX-kP.js +0 -1
  226. package/payload/server/public/assets/infoDiagram-42DDH7IO-KHcXRaan.js +0 -2
  227. package/payload/server/public/assets/operator-BZeeyait.js +0 -1
  228. package/payload/server/public/assets/packet-4T2RLAQJ-CTU0gCGb.js +0 -1
  229. package/payload/server/public/assets/page-B0obcg3b.js +0 -1
  230. package/payload/server/public/assets/pie-ZZUOXDRM-D-EEHxhC.js +0 -1
  231. package/payload/server/public/assets/public-9D3MMOkC.js +0 -1
  232. package/payload/server/public/assets/radar-PYXPWWZC-DHXATZGB.js +0 -1
  233. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CxOIKvXh.js +0 -1
  234. package/payload/server/public/assets/tasks-BB0L7e5a.js +0 -1
  235. package/payload/server/public/assets/treeView-SZITEDCU-CC7ouMFd.js +0 -1
  236. package/payload/server/public/assets/treemap-W4RFUUIX-CaCg27im.js +0 -1
  237. package/payload/server/public/assets/useCopyFeedback-jTBQ2h0V.js +0 -1
  238. package/payload/server/public/assets/useSelectionMode-BooArySZ.js +0 -1
  239. package/payload/server/public/assets/useSubAccountSwitcher-D9knHOKq.css +0 -1
  240. package/payload/server/public/assets/useVoiceRecorder-PUT8q_sm.js +0 -2
  241. package/payload/server/public/assets/wardley-RL74JXVD-bMPsqQZ7.js +0 -1
  242. /package/payload/server/public/assets/{_baseFor-k8hYPe-U.js → _baseFor-Bjj9It3Q.js} +0 -0
  243. /package/payload/server/public/assets/{array-BFDiaBgf.js → array-BrrxEo12.js} +0 -0
  244. /package/payload/server/public/assets/{cytoscape.esm-Ch0xptA4.js → cytoscape.esm-CTWJxUxo.js} +0 -0
  245. /package/payload/server/public/assets/{defaultLocale-ZeknFqNe.js → defaultLocale-CFDbRV4k.js} +0 -0
  246. /package/payload/server/public/assets/{dist-B2N0v8hZ.js → dist-BATfy7Sn.js} +0 -0
  247. /package/payload/server/public/assets/{init-0VJVrkRJ.js → init-dXCzGgLP.js} +0 -0
  248. /package/payload/server/public/assets/{katex-Br7bH10N.js → katex-CHA1JFYJ.js} +0 -0
  249. /package/payload/server/public/assets/{path-INs8XTPH.js → path-CCRpdXhK.js} +0 -0
  250. /package/payload/server/public/assets/{preload-helper-D_sPnjeL.js → preload-helper-CH6UZRzu.js} +0 -0
  251. /package/payload/server/public/assets/{rough.esm-DU9bZLvZ.js → rough.esm-Bud4g4fh.js} +0 -0
  252. /package/payload/server/public/assets/{src-DHSQDYx-.js → src-DNdBRXXx.js} +0 -0
@@ -133,7 +133,7 @@ import {
133
133
  registerResource,
134
134
  resolveOwner,
135
135
  tooLargeMessage
136
- } from "./chunk-3XLLTG6R.js";
136
+ } from "./chunk-REWXLAPC.js";
137
137
  import {
138
138
  __commonJS,
139
139
  __toESM
@@ -652,7 +652,17 @@ var require_dist3 = __commonJS({
652
652
  Risk: "#C15B57",
653
653
  Source: "#2F8FA6",
654
654
  Finding: "#4FA389",
655
- Hypothesis: "#9A8C3E"
655
+ Hypothesis: "#9A8C3E",
656
+ // Ledger cash movement (schema-base.md, added by 7b225f814). One movement
657
+ // of money attached to no invoice — operator-entry with no parent to drill
658
+ // down from, so it is a top-level chip. Burnished olive-gold sits in the
659
+ // money register beside Invoice's brown without colliding with it.
660
+ //
661
+ // Deliberately appended at the END rather than beside the ledger family:
662
+ // SHAPE_BY_LABEL assigns shapes by index position in ALL_GRAPH_LABELS
663
+ // (this object's key order), so a mid-table insert would silently change
664
+ // the rendered shape of every label after it.
665
+ CashEntry: "#7D5A2B"
656
666
  };
657
667
  exports.FALLBACK_LABEL_COLOUR = "#94A3B8";
658
668
  exports.ALL_GRAPH_LABELS = Object.freeze(Object.keys(exports.GRAPH_LABEL_COLOURS));
@@ -749,7 +759,8 @@ var require_dist3 = __commonJS({
749
759
  Risk: '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path>',
750
760
  Source: '<path d="M12 7v14"></path><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"></path>',
751
761
  Finding: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"></path><path d="M9 18h6"></path><path d="M10 22h4"></path>',
752
- Hypothesis: '<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"></path><path d="M6.453 15h11.094"></path><path d="M8.5 2h7"></path>'
762
+ Hypothesis: '<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"></path><path d="M6.453 15h11.094"></path><path d="M8.5 2h7"></path>',
763
+ CashEntry: '<rect width="20" height="12" x="2" y="6" rx="2"></rect><circle cx="12" cy="12" r="2"></circle><path d="M6 12h.01"></path><path d="M18 12h.01"></path>'
753
764
  };
754
765
  function iconForLabel2(label) {
755
766
  return Object.prototype.hasOwnProperty.call(LABEL_ICONS, label) ? LABEL_ICONS[label] : null;
@@ -845,6 +856,11 @@ var require_dist3 = __commonJS({
845
856
  if (clientName.length > 0) {
846
857
  return clientName.length > 24 ? clientName.slice(0, 24) + "\u2026" : clientName;
847
858
  }
859
+ } else if (primaryLabel2 === "CashEntry") {
860
+ const desc = typeof props.description === "string" ? props.description : "";
861
+ if (desc.length > 0) {
862
+ return desc.length > 24 ? desc.slice(0, 24) + "\u2026" : desc;
863
+ }
848
864
  } else if (primaryLabel2 === "Message") {
849
865
  const role = typeof props.role === "string" ? props.role : "";
850
866
  const roleShort = role === "user" ? "user" : role === "assistant" ? "asst" : role === "system" ? "sys" : role === "tool" ? "tool" : null;
@@ -918,6 +934,10 @@ var require_dist3 = __commonJS({
918
934
  const clientName = typeof props.clientName === "string" ? props.clientName : "";
919
935
  if (clientName.length > 0)
920
936
  return clientName;
937
+ } else if (primaryLabel2 === "CashEntry") {
938
+ const desc = typeof props.description === "string" ? props.description : "";
939
+ if (desc.length > 0)
940
+ return desc;
921
941
  }
922
942
  for (const k of ["name", "title", "summary", "givenName", "subject", "text"]) {
923
943
  const v = props[k];
@@ -1291,8 +1311,8 @@ var require_dist4 = __commonJS({
1291
1311
  let edgesCreated = 0;
1292
1312
  for (const rel of relationships) {
1293
1313
  const type = rel.type.replace(/`/g, "");
1294
- const q = rel.direction === "outgoing" ? `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (a)-[:\`${type}\`]->(b)` : `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (b)-[:\`${type}\`]->(a)`;
1295
- const r = await tx.run(q, { from: nodeId, to: rel.targetNodeId });
1314
+ const q2 = rel.direction === "outgoing" ? `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (a)-[:\`${type}\`]->(b)` : `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (b)-[:\`${type}\`]->(a)`;
1315
+ const r = await tx.run(q2, { from: nodeId, to: rel.targetNodeId });
1296
1316
  const created = r.summary.counters.updates().relationshipsCreated;
1297
1317
  if (created === 0) {
1298
1318
  process.stderr.write(`[graph-write] reject reason=unresolved-target-on-create labels=${labelCsv} agent=${agentLabel} relType=${rel.type} targetId=${rel.targetNodeId}
@@ -1540,10 +1560,10 @@ var serveStatic = (options = { root: "" }) => {
1540
1560
  };
1541
1561
 
1542
1562
  // server/index.ts
1543
- import { readFileSync as readFileSync41, existsSync as existsSync38, watchFile, readdirSync as readdirSync26, statSync as statSync17 } from "fs";
1563
+ import { readFileSync as readFileSync42, existsSync as existsSync38, watchFile, readdirSync as readdirSync26, statSync as statSync17 } from "fs";
1544
1564
  import { spawn as spawn3 } from "child_process";
1545
1565
  import { createHash as createHash7 } from "crypto";
1546
- import { resolve as resolve36, join as join42, basename as basename12 } from "path";
1566
+ import { resolve as resolve37, join as join44, basename as basename12 } from "path";
1547
1567
  import { homedir as homedir4 } from "os";
1548
1568
  import { monitorEventLoopDelay } from "perf_hooks";
1549
1569
 
@@ -1958,14 +1978,14 @@ function readAllConversationsCached(accountId) {
1958
1978
  for (const file of dir) {
1959
1979
  if (!file.endsWith(".jsonl")) continue;
1960
1980
  const path3 = join4(STORE_ROOT(), accountId, file);
1961
- let stat8;
1981
+ let stat9;
1962
1982
  try {
1963
- stat8 = statSync3(path3);
1983
+ stat9 = statSync3(path3);
1964
1984
  } catch {
1965
1985
  continue;
1966
1986
  }
1967
1987
  const hit = idCache.get(path3);
1968
- if (hit && hit.size === stat8.size && hit.mtimeMs === stat8.mtimeMs) {
1988
+ if (hit && hit.size === stat9.size && hit.mtimeMs === stat9.mtimeMs) {
1969
1989
  out.set(file, hit.ids);
1970
1990
  continue;
1971
1991
  }
@@ -1982,7 +2002,7 @@ function readAllConversationsCached(accountId) {
1982
2002
  } catch {
1983
2003
  continue;
1984
2004
  }
1985
- idCache.set(path3, { size: stat8.size, mtimeMs: stat8.mtimeMs, ids: parsed });
2005
+ idCache.set(path3, { size: stat9.size, mtimeMs: stat9.mtimeMs, ids: parsed });
1986
2006
  out.set(file, parsed);
1987
2007
  }
1988
2008
  return out;
@@ -2816,7 +2836,7 @@ var credsSaveQueue = Promise.resolve();
2816
2836
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
2817
2837
  console.error(`${TAG6} draining credential save queue\u2026`);
2818
2838
  const timer = new Promise(
2819
- (resolve37) => setTimeout(() => resolve37("timeout"), timeoutMs)
2839
+ (resolve38) => setTimeout(() => resolve38("timeout"), timeoutMs)
2820
2840
  );
2821
2841
  const result = await Promise.race([
2822
2842
  credsSaveQueue.then(() => "drained"),
@@ -2993,11 +3013,11 @@ async function createWaSocket(opts) {
2993
3013
  return sock;
2994
3014
  }
2995
3015
  async function waitForConnection(sock) {
2996
- return new Promise((resolve37, reject) => {
3016
+ return new Promise((resolve38, reject) => {
2997
3017
  const handler = (update) => {
2998
3018
  if (update.connection === "open") {
2999
3019
  sock.ev.off("connection.update", handler);
3000
- resolve37();
3020
+ resolve38();
3001
3021
  }
3002
3022
  if (update.connection === "close") {
3003
3023
  sock.ev.off("connection.update", handler);
@@ -3105,7 +3125,7 @@ function isTimeoutError(err) {
3105
3125
  return Boolean(err?.__maxyTimeout);
3106
3126
  }
3107
3127
  function withTimeout(label, promise, timeoutMs) {
3108
- return new Promise((resolve37, reject) => {
3128
+ return new Promise((resolve38, reject) => {
3109
3129
  const timer = setTimeout(() => {
3110
3130
  const err = new Error(`${label} timed out after ${timeoutMs}ms`);
3111
3131
  err.__maxyTimeout = true;
@@ -3115,7 +3135,7 @@ function withTimeout(label, promise, timeoutMs) {
3115
3135
  promise.then(
3116
3136
  (value) => {
3117
3137
  clearTimeout(timer);
3118
- resolve37(value);
3138
+ resolve38(value);
3119
3139
  },
3120
3140
  (err) => {
3121
3141
  clearTimeout(timer);
@@ -4029,15 +4049,15 @@ function getChannelActivity(accountId) {
4029
4049
 
4030
4050
  // app/lib/whatsapp/outbound/send.ts
4031
4051
  var TAG10 = "[whatsapp:outbound]";
4032
- function buildQuoted(q) {
4052
+ function buildQuoted(q2) {
4033
4053
  return {
4034
4054
  key: {
4035
- remoteJid: q.remoteJid,
4036
- id: q.id,
4037
- fromMe: q.fromMe,
4038
- ...q.participant ? { participant: q.participant } : {}
4055
+ remoteJid: q2.remoteJid,
4056
+ id: q2.id,
4057
+ fromMe: q2.fromMe,
4058
+ ...q2.participant ? { participant: q2.participant } : {}
4039
4059
  },
4040
- message: { conversation: q.body ?? "" }
4060
+ message: { conversation: q2.body ?? "" }
4041
4061
  };
4042
4062
  }
4043
4063
  async function sendTextMessage(sock, to, text, opts) {
@@ -4155,8 +4175,8 @@ async function persistWhatsAppMessage(input) {
4155
4175
  const { givenName, familyName } = splitName(input.pushName);
4156
4176
  const prev = sessionWriteLocks.get(input.cacheKey);
4157
4177
  let release;
4158
- const mine = new Promise((resolve37) => {
4159
- release = resolve37;
4178
+ const mine = new Promise((resolve38) => {
4179
+ release = resolve38;
4160
4180
  });
4161
4181
  const chained = (prev ?? Promise.resolve()).then(() => mine);
4162
4182
  sessionWriteLocks.set(input.cacheKey, chained);
@@ -4280,7 +4300,7 @@ async function ensureWhatsAppConversation(input) {
4280
4300
  return null;
4281
4301
  }
4282
4302
  console.error(
4283
- `${TAG12} conversation-merged cacheKey=${input.cacheKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
4303
+ `${TAG12} conversation-merged cacheKey=${input.cacheKey} agentType=${input.agentType} channel=whatsapp accountId=${input.platformAccountId} connectionAccountId=${input.connectionAccountId ?? input.platformAccountId} created=${result.created} ms=${ms}`
4284
4304
  );
4285
4305
  return { sessionId: result.sessionId, created: result.created };
4286
4306
  } catch (err) {
@@ -5642,6 +5662,29 @@ function selectStoreAccountKey(conns, accountId) {
5642
5662
  function resolveReadStoreKey(accountId) {
5643
5663
  return selectStoreAccountKey(Array.from(connections.values()), accountId);
5644
5664
  }
5665
+ function resolveInboundPersistAccount(args) {
5666
+ const result = args.accessResult;
5667
+ if (result?.allowed && result.reason === "account-manager" && result.effectiveAccountId) {
5668
+ return result.effectiveAccountId;
5669
+ }
5670
+ return args.connPlatformAccountId;
5671
+ }
5672
+ function resolveConversationPersistAccount(sendAccountId, counterpartyPhone) {
5673
+ const conn = findConnectionByKey(Array.from(connections.values()), sendAccountId);
5674
+ if (!conn) return sendAccountId;
5675
+ const access = checkDmAccess({
5676
+ senderPhone: counterpartyPhone,
5677
+ selfPhone: conn.selfPhone ?? "",
5678
+ config: whatsAppConfig,
5679
+ accountConfig: whatsAppConfig.accounts?.[conn.accountId],
5680
+ accountId: conn.accountId,
5681
+ isValidAccount: (id) => isValidAccountId(id)
5682
+ });
5683
+ return resolveInboundPersistAccount({
5684
+ accessResult: access,
5685
+ connPlatformAccountId: conn.platformAccountId
5686
+ });
5687
+ }
5645
5688
  function listConnectionPlatformAccountIds() {
5646
5689
  return [...connections.values()].map((conn) => conn.platformAccountId);
5647
5690
  }
@@ -5860,11 +5903,11 @@ async function connectWithReconnect(conn) {
5860
5903
  console.error(
5861
5904
  `${TAG17} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
5862
5905
  );
5863
- await new Promise((resolve37) => {
5864
- const timer = setTimeout(resolve37, delay);
5906
+ await new Promise((resolve38) => {
5907
+ const timer = setTimeout(resolve38, delay);
5865
5908
  conn.abortController.signal.addEventListener("abort", () => {
5866
5909
  clearTimeout(timer);
5867
- resolve37();
5910
+ resolve38();
5868
5911
  }, { once: true });
5869
5912
  });
5870
5913
  }
@@ -5872,16 +5915,16 @@ async function connectWithReconnect(conn) {
5872
5915
  }
5873
5916
  }
5874
5917
  function waitForDisconnectEvent(conn) {
5875
- return new Promise((resolve37) => {
5918
+ return new Promise((resolve38) => {
5876
5919
  if (!conn.sock) {
5877
- resolve37();
5920
+ resolve38();
5878
5921
  return;
5879
5922
  }
5880
5923
  const sock = conn.sock;
5881
5924
  const handler = (update) => {
5882
5925
  if (update.connection === "close") {
5883
5926
  sock.ev.off("connection.update", handler);
5884
- resolve37();
5927
+ resolve38();
5885
5928
  }
5886
5929
  };
5887
5930
  sock.ev.on("connection.update", handler);
@@ -5948,6 +5991,7 @@ function monitorInbound(conn) {
5948
5991
  if (!remoteJid || remoteJid === "status@broadcast") continue;
5949
5992
  if (!msg.message) continue;
5950
5993
  let receiptDeferredToWorker = false;
5994
+ let dmAccess = null;
5951
5995
  const extracted = extractMessage(msg);
5952
5996
  const mediaServable = !!(extracted.mediaType && extracted.mimetype);
5953
5997
  if (!extracted.text && !mediaServable && msg.key.id) {
@@ -5962,7 +6006,21 @@ function monitorInbound(conn) {
5962
6006
  const senderJid = isGroup ? msg.key.participant ?? remoteJid : remoteJid;
5963
6007
  const senderPhone = await resolveJidToE164(senderJid, conn.lidMapping) ?? senderJid;
5964
6008
  const ts = msg.messageTimestamp ? Number(msg.messageTimestamp) : Math.floor(Date.now() / 1e3);
5965
- const storeKey = messageStoreKey(conn.platformAccountId, remoteJid);
6009
+ const fromMe = Boolean(msg.key.fromMe);
6010
+ dmAccess = isGroup ? null : checkDmAccess({
6011
+ senderPhone,
6012
+ selfPhone: conn.selfPhone ?? "",
6013
+ config: whatsAppConfig,
6014
+ accountConfig: whatsAppConfig.accounts?.[conn.accountId],
6015
+ accountId: conn.accountId,
6016
+ isValidAccount: (id) => isValidAccountId(id)
6017
+ });
6018
+ const persistAccountId = resolveInboundPersistAccount({
6019
+ accessResult: dmAccess,
6020
+ connPlatformAccountId: conn.platformAccountId
6021
+ });
6022
+ const managerScoped = dmAccess?.allowed === true && dmAccess.reason === "account-manager";
6023
+ const storeKey = messageStoreKey(persistAccountId, remoteJid);
5966
6024
  if (extracted.text) {
5967
6025
  storeMessage(storeKey, {
5968
6026
  id: msg.key.id ?? `${remoteJid}-${ts}`,
@@ -5983,16 +6041,16 @@ function monitorInbound(conn) {
5983
6041
  direction: "inbound",
5984
6042
  jid: remoteJid
5985
6043
  });
5986
- const fromMe = Boolean(msg.key.fromMe);
5987
- const cacheKeyAgentType = isGroup ? "public" : fromMe ? "admin" : checkDmAccess({
5988
- senderPhone,
5989
- selfPhone: conn.selfPhone ?? "",
5990
- config: whatsAppConfig,
5991
- accountConfig: whatsAppConfig.accounts?.[conn.accountId]
5992
- }).agentType;
6044
+ const cacheKeyAgentType = isGroup ? "public" : fromMe ? "admin" : dmAccess?.agentType ?? "public";
5993
6045
  const cacheKey = deriveCacheKey({
5994
6046
  agentType: cacheKeyAgentType,
5995
- accountId: conn.accountId,
6047
+ // Task 1786 — an account-manager keys on its bound sub-account, so a
6048
+ // manager gets its own `:Conversation` instead of merging into the
6049
+ // owner's bare `whatsapp:<dirname>` owner-mirror row. `deriveCacheKey`
6050
+ // returns `whatsapp:<accountId>` for admin; a UUID contains no colon,
6051
+ // so the key stays 2 segments and `deriveScope` still reads "admin".
6052
+ // Every non-manager case passes conn.accountId, byte-identical.
6053
+ accountId: managerScoped ? persistAccountId : conn.accountId,
5996
6054
  senderPhone,
5997
6055
  isGroup,
5998
6056
  groupJid: isGroup ? remoteJid : void 0,
@@ -6001,7 +6059,10 @@ function monitorInbound(conn) {
6001
6059
  if (msg.key.id) {
6002
6060
  const merged = await ensureWhatsAppConversation({
6003
6061
  accountId: conn.accountId,
6004
- platformAccountId: conn.platformAccountId,
6062
+ platformAccountId: persistAccountId,
6063
+ // Task 1786 — the socket owner, logged beside the stamped account
6064
+ // so a divergence between the two is one greppable line.
6065
+ connectionAccountId: conn.platformAccountId,
6005
6066
  cacheKey,
6006
6067
  agentType: cacheKeyAgentType,
6007
6068
  groupJid: isGroup ? remoteJid : void 0
@@ -6013,7 +6074,12 @@ function monitorInbound(conn) {
6013
6074
  );
6014
6075
  const alreadyRecorded = fromMe && echoAlreadyRecorded({
6015
6076
  readConversation,
6016
- platformAccountId: conn.platformAccountId,
6077
+ // Task 1786 — read the account /reply now APPENDS under. The
6078
+ // echo gate asks "did /reply already store this?", so it must
6079
+ // look in the same store file the reply landed in; reading the
6080
+ // socket owner's file would report every manager echo as
6081
+ // unrecorded and store a duplicate copy.
6082
+ platformAccountId: persistAccountId,
6017
6083
  canonicalRemoteJid,
6018
6084
  accountId: conn.accountId,
6019
6085
  remoteJid,
@@ -6032,7 +6098,7 @@ function monitorInbound(conn) {
6032
6098
  });
6033
6099
  const basePersist = {
6034
6100
  accountId: conn.accountId,
6035
- platformAccountId: conn.platformAccountId,
6101
+ platformAccountId: persistAccountId,
6036
6102
  remoteJid,
6037
6103
  canonicalRemoteJid,
6038
6104
  cacheKey,
@@ -6051,7 +6117,7 @@ function monitorInbound(conn) {
6051
6117
  };
6052
6118
  if (wantsServableMedia && upsert.type === "append") {
6053
6119
  const sock2 = conn.sock;
6054
- const platformAccountId = conn.platformAccountId;
6120
+ const platformAccountId = persistAccountId;
6055
6121
  const caption = extracted.caption ?? "";
6056
6122
  const fallbackBody = mediaFallbackBody(extracted);
6057
6123
  const msgKeyId = msg.key.id;
@@ -6089,7 +6155,8 @@ function monitorInbound(conn) {
6089
6155
  mediaAttachmentId = await storeInboundMediaServable(
6090
6156
  msg,
6091
6157
  conn.sock,
6092
- conn.platformAccountId,
6158
+ // Task 1786 — attachment bytes follow the record's account.
6159
+ persistAccountId,
6093
6160
  mediaSeed,
6094
6161
  { maxBytes }
6095
6162
  ) ?? void 0;
@@ -6291,8 +6358,8 @@ async function handleInboundMessage(conn, msg) {
6291
6358
  const conversationKey = isGroup ? remoteJid : senderPhone;
6292
6359
  const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
6293
6360
  let resolvePending;
6294
- const sttPending = new Promise((resolve37) => {
6295
- resolvePending = resolve37;
6361
+ const sttPending = new Promise((resolve38) => {
6362
+ resolvePending = resolve38;
6296
6363
  });
6297
6364
  if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
6298
6365
  try {
@@ -6720,20 +6787,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
6720
6787
 
6721
6788
  // server/routes/health.ts
6722
6789
  function checkPort(port2, timeoutMs = 500) {
6723
- return new Promise((resolve37) => {
6790
+ return new Promise((resolve38) => {
6724
6791
  const socket = createConnection2(port2, "127.0.0.1");
6725
6792
  socket.setTimeout(timeoutMs);
6726
6793
  socket.once("connect", () => {
6727
6794
  socket.destroy();
6728
- resolve37(true);
6795
+ resolve38(true);
6729
6796
  });
6730
6797
  socket.once("error", () => {
6731
6798
  socket.destroy();
6732
- resolve37(false);
6799
+ resolve38(false);
6733
6800
  });
6734
6801
  socket.once("timeout", () => {
6735
6802
  socket.destroy();
6736
- resolve37(false);
6803
+ resolve38(false);
6737
6804
  });
6738
6805
  });
6739
6806
  }
@@ -6988,8 +7055,8 @@ function webchatTurnTimeoutMs() {
6988
7055
  return Number(process.env.WEBCHAT_TURN_TIMEOUT_MS ?? String(2 * 6e4));
6989
7056
  }
6990
7057
  function createChatRoutes(deps) {
6991
- const app64 = new Hono();
6992
- app64.post("/", async (c) => {
7058
+ const app65 = new Hono();
7059
+ app65.post("/", async (c) => {
6993
7060
  console.log(`[chat-route] entered route=public method=POST`);
6994
7061
  const contentType = c.req.header("content-type") ?? "";
6995
7062
  const account = resolveAccount();
@@ -7253,7 +7320,7 @@ ${result.result.text}` : result.result.text;
7253
7320
  }
7254
7321
  });
7255
7322
  });
7256
- return app64;
7323
+ return app65;
7257
7324
  }
7258
7325
 
7259
7326
  // app/lib/channel-pty-bridge/admin-session-id.ts
@@ -7287,8 +7354,8 @@ function isLoopbackAddr(addr) {
7287
7354
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
7288
7355
  }
7289
7356
  function createScheduleInjectRoutes(deps) {
7290
- const app64 = new Hono();
7291
- app64.post("/", async (c) => {
7357
+ const app65 = new Hono();
7358
+ app65.post("/", async (c) => {
7292
7359
  const env = c.env;
7293
7360
  const remoteAddr = env?.incoming?.socket?.remoteAddress ?? "";
7294
7361
  if (!isLoopbackAddr(remoteAddr)) {
@@ -7389,7 +7456,7 @@ function createScheduleInjectRoutes(deps) {
7389
7456
  console.error(`${TAG20} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=ok status=-`);
7390
7457
  return c.json({ ok: true }, 200);
7391
7458
  });
7392
- return app64;
7459
+ return app65;
7393
7460
  }
7394
7461
 
7395
7462
  // server/index.ts
@@ -8022,8 +8089,8 @@ async function startLogin(opts) {
8022
8089
  await clearAuth(authDir);
8023
8090
  let resolveCode = null;
8024
8091
  let rejectCode = null;
8025
- const codePromise = new Promise((resolve37, reject) => {
8026
- resolveCode = resolve37;
8092
+ const codePromise = new Promise((resolve38, reject) => {
8093
+ resolveCode = resolve38;
8027
8094
  rejectCode = reject;
8028
8095
  });
8029
8096
  const codeTimer = setTimeout(
@@ -9321,7 +9388,10 @@ app3.post("/pages/deploy", pagesDeployBodyLimit, async (c) => {
9321
9388
  `[pages-broker] op=project-ensure pub=${pub} project=${project} existed=${existed} created=${!existed}`
9322
9389
  );
9323
9390
  const started = Date.now();
9324
- const { url } = await exec.pagesDeploy(dir, project, branch);
9391
+ const { url, excluded } = await exec.pagesDeploy(dir, project, branch);
9392
+ console.error(
9393
+ `[pages-broker] op=stage pub=${pub} excluded=${excluded.length > 0 ? excluded.join(",") : "none"}`
9394
+ );
9325
9395
  console.error(`[pages-broker] op=deploy pub=${pub} ranMs=${Date.now() - started} url=${url}`);
9326
9396
  const httpStatus = await verifyDeployed(url);
9327
9397
  const verified = httpStatus === 200;
@@ -9638,7 +9708,7 @@ app3.post("/r2/object/delete", envelopeBodyLimit("write"), async (c) => {
9638
9708
  });
9639
9709
  var storage_broker_default = app3;
9640
9710
  async function runStorageAudit(root) {
9641
- const { reconcileStorage } = await import("./src-XQ63FPRE.js");
9711
+ const { reconcileStorage } = await import("./src-JJ6PHO5I.js");
9642
9712
  const cf = await makeHouseCfExec(root);
9643
9713
  const accountsDir = resolve11(root, "..", "data", "accounts");
9644
9714
  const strays = (await reconcileStorage({
@@ -9678,7 +9748,7 @@ async function runStorageAudit(root) {
9678
9748
  );
9679
9749
  }
9680
9750
  async function runPagesAudit(root) {
9681
- const { reconcilePages } = await import("./src-XQ63FPRE.js");
9751
+ const { reconcilePages } = await import("./src-JJ6PHO5I.js");
9682
9752
  const cf = await makeHousePagesExec(root);
9683
9753
  const r = await reconcilePages({
9684
9754
  cfProjects: async () => (await cf.pagesProjectList()).map((p) => p.name),
@@ -10961,6 +11031,9 @@ function readFrom(path3, from) {
10961
11031
  closeSync(fd);
10962
11032
  }
10963
11033
  }
11034
+ function resolveReplyAppendAccount(args) {
11035
+ return args.resolve(args.sendAccountId, args.counterpartyPhone) || args.sendAccountId;
11036
+ }
10964
11037
  function resumeOffset(lastEventId, fileEnd) {
10965
11038
  if (!lastEventId) return 0;
10966
11039
  const n = Number(lastEventId);
@@ -11337,12 +11410,12 @@ app5.get("/store-stream", requireAdminSession, (c) => {
11337
11410
  ...reactions && reactions.length > 0 ? { reactions } : {}
11338
11411
  };
11339
11412
  if (r.quotedId) {
11340
- const q = byMsgKeyId.get(r.quotedId);
11413
+ const q2 = byMsgKeyId.get(r.quotedId);
11341
11414
  turn = {
11342
11415
  ...turn,
11343
- quoted: q ? { body: q.body, sender: q.senderName ?? (q.fromMe ? null : q.senderTelephone || null), fromMe: q.fromMe } : { body: "", sender: r.quotedSender, fromMe: false }
11416
+ quoted: q2 ? { body: q2.body, sender: q2.senderName ?? (q2.fromMe ? null : q2.senderTelephone || null), fromMe: q2.fromMe } : { body: "", sender: r.quotedSender, fromMe: false }
11344
11417
  };
11345
- console.log(`[wa-reader] op=store-quote msgId=${r.msgKeyId} quoted=${!!q}`);
11418
+ console.log(`[wa-reader] op=store-quote msgId=${r.msgKeyId} quoted=${!!q2}`);
11346
11419
  }
11347
11420
  if (r.attachmentId) {
11348
11421
  const meta = readAttachmentMeta(join19(uploadsBase, r.attachmentId));
@@ -11650,9 +11723,14 @@ app5.post("/reply", requireAdminSession, async (c) => {
11650
11723
  // so the store reader renders it inline instead of a `[sent …]` text line.
11651
11724
  ...attachmentId ? { attachmentId } : {}
11652
11725
  };
11653
- appendMessage(accountId, rec);
11654
- const present = readConversation(accountId, remoteJid).some((r) => r.messageId === rec.messageId);
11655
- console.log(`${MANUAL_TAG} op=recorded sendId=${sendId} waMessageId=${waMessageId} present=${present} attachment=${attachmentId ? "yes" : "no"}`);
11726
+ const appendAccountId = resolveReplyAppendAccount({
11727
+ sendAccountId: accountId,
11728
+ counterpartyPhone: remoteJid.split("@")[0],
11729
+ resolve: resolveConversationPersistAccount
11730
+ });
11731
+ appendMessage(appendAccountId, rec);
11732
+ const present = readConversation(appendAccountId, remoteJid).some((r) => r.messageId === rec.messageId);
11733
+ console.log(`${MANUAL_TAG} op=recorded sendId=${sendId} waMessageId=${waMessageId} present=${present} appendAccount=${appendAccountId.slice(0, 8)} sendAccount=${acct8} attachment=${attachmentId ? "yes" : "no"}`);
11656
11734
  };
11657
11735
  let sent = 0;
11658
11736
  const sendStagedFile = async (file, label) => {
@@ -12252,10 +12330,7 @@ function locateSidecar(sessionId) {
12252
12330
  }
12253
12331
  for (const slug of slugs) {
12254
12332
  const metaPath = join23(projectsRoot, slug, `${sessionId}.meta.json`);
12255
- if (existsSync15(metaPath)) {
12256
- const meta = readSidecarMeta(metaPath);
12257
- return { channel: meta.channel, adminUserId: meta.adminUserId, accountId: meta.accountId };
12258
- }
12333
+ if (existsSync15(metaPath)) return readSidecarMeta(metaPath);
12259
12334
  }
12260
12335
  return null;
12261
12336
  }
@@ -12268,6 +12343,13 @@ function readTargetOwner(sessionId) {
12268
12343
  }
12269
12344
  return { found: false, owner: null };
12270
12345
  }
12346
+ function readSourceRowMeta(sessionId) {
12347
+ const sidecar = locateSidecar(sessionId);
12348
+ if (sidecar !== null) return sidecar;
12349
+ const { projectDir } = locateSession(sessionId);
12350
+ if (projectDir !== null) return readSidecarMeta(join23(projectDir, `${sessionId}.meta.json`));
12351
+ return null;
12352
+ }
12271
12353
  function ownedByRequester(owner, requesterUserId, primaryUserId) {
12272
12354
  if (requesterUserId === null) return false;
12273
12355
  return owner === requesterUserId || owner === null && requesterUserId === primaryUserId;
@@ -12476,8 +12558,8 @@ async function reapplyLeversViaManager() {
12476
12558
  var WEBCHAT_SEND_TURN_WINDOW_MS = Number(process.env.WEBCHAT_SEND_TURN_WINDOW_MS ?? String(15e3));
12477
12559
  var sendSeq = 0;
12478
12560
  function createWebchatRoutes(deps) {
12479
- const app64 = new Hono();
12480
- app64.post("/send", requireAdminSession, async (c) => {
12561
+ const app65 = new Hono();
12562
+ app65.post("/send", requireAdminSession, async (c) => {
12481
12563
  let accountId;
12482
12564
  try {
12483
12565
  accountId = resolvePlatformAccountId();
@@ -12689,7 +12771,7 @@ ${note}` : note;
12689
12771
  if (turnTimer.unref) turnTimer.unref();
12690
12772
  return c.json({ ok: true });
12691
12773
  });
12692
- app64.post("/interrupt", requireAdminSession, async (c) => {
12774
+ app65.post("/interrupt", requireAdminSession, async (c) => {
12693
12775
  const cacheKey = c.get("cacheKey");
12694
12776
  const requesterUserId = (cacheKey ? getUserIdForSession(cacheKey) : void 0) ?? null;
12695
12777
  const primaryUserId = resolvePrimaryAdminUserId();
@@ -12717,7 +12799,7 @@ ${note}` : note;
12717
12799
  }
12718
12800
  return c.json({ ok: true, stopped: outcome.stopped, intId: outcome.intId, deadChild: outcome.deadChild }, 200);
12719
12801
  });
12720
- app64.post("/settings", requireAdminSession, async (c) => {
12802
+ app65.post("/settings", requireAdminSession, async (c) => {
12721
12803
  const body = await c.req.json().catch(() => null);
12722
12804
  const op = body?.op;
12723
12805
  const value = body?.value;
@@ -12757,7 +12839,7 @@ ${note}` : note;
12757
12839
  console.log(`[webchat:settings] op=${op} outcome=accepted value=${value} settingsApplied=${settingsApplied}`);
12758
12840
  return c.json({ ok: true, settingsApplied });
12759
12841
  });
12760
- app64.get("/session", requireAdminSession, async (c) => {
12842
+ app65.get("/session", requireAdminSession, async (c) => {
12761
12843
  let accountId;
12762
12844
  try {
12763
12845
  accountId = resolvePlatformAccountId();
@@ -12835,7 +12917,7 @@ ${note}` : note;
12835
12917
  const indicators = await fetchComposerIndicators(sessionId);
12836
12918
  return c.json({ sessionId, projectDir, source, sizeBytes: jsonlSizeBytes(projectDir, sessionId), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${sessionId}`) ?? null, deliveryFailure: deps.deliveryFailureFor?.(`session:${sessionId}`) ?? null, ...indicators, ...readLevers(account) });
12837
12919
  });
12838
- app64.post("/permission-verdict", requireAdminSession, async (c) => {
12920
+ app65.post("/permission-verdict", requireAdminSession, async (c) => {
12839
12921
  const body = await c.req.json().catch(() => null);
12840
12922
  const sessionId = body?.sessionId;
12841
12923
  const requestId = body?.request_id;
@@ -12852,7 +12934,7 @@ ${note}` : note;
12852
12934
  const ok = deps.resolvePermissionVerdict?.(`session:${sessionId}`, requestId, behavior) ?? false;
12853
12935
  return c.json({ ok });
12854
12936
  });
12855
- return app64;
12937
+ return app65;
12856
12938
  }
12857
12939
 
12858
12940
  // server/routes/webchat-greeting.ts
@@ -14208,15 +14290,15 @@ function operatorRoleFor(config, userId) {
14208
14290
  async function buildAccountOptionList(accounts, userId) {
14209
14291
  const houseAccount = accounts.find((a) => a.config.role === "house");
14210
14292
  console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
14211
- const resolve37 = async (a) => {
14293
+ const resolve38 = async (a) => {
14212
14294
  const businessName = await fetchAccountName(a.accountId) || void 0;
14213
14295
  return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
14214
14296
  };
14215
14297
  const houseOptions = await Promise.all(
14216
- accounts.filter((a) => a.config.role === "house").map(resolve37)
14298
+ accounts.filter((a) => a.config.role === "house").map(resolve38)
14217
14299
  );
14218
14300
  const nonHouseOptions = await Promise.all(
14219
- accounts.filter((a) => a.config.role !== "house").map(resolve37)
14301
+ accounts.filter((a) => a.config.role !== "house").map(resolve38)
14220
14302
  );
14221
14303
  const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
14222
14304
  nonHouseOptions.sort((x, y) => {
@@ -15223,9 +15305,9 @@ app18.post("/:id/resume", async (c) => {
15223
15305
  if (healQueue.length > 0) {
15224
15306
  void (async () => {
15225
15307
  for (let i = 0; i < healQueue.length; i++) {
15226
- const q = healQueue[i];
15308
+ const q2 = healQueue[i];
15227
15309
  try {
15228
- const healComponents = await Promise.all(q.components.map(async (c2) => {
15310
+ const healComponents = await Promise.all(q2.components.map(async (c2) => {
15229
15311
  const base = {
15230
15312
  componentId: c2.componentId,
15231
15313
  name: c2.name,
@@ -15263,25 +15345,25 @@ app18.post("/:id/resume", async (c) => {
15263
15345
  }));
15264
15346
  const messageId = await persistMessage(
15265
15347
  sessionId,
15266
- q.role,
15267
- q.content,
15348
+ q2.role,
15349
+ q2.content,
15268
15350
  accountId,
15269
15351
  void 0,
15270
- q.createdAt,
15352
+ q2.createdAt,
15271
15353
  void 0,
15272
15354
  healComponents,
15273
15355
  void 0
15274
15356
  );
15275
15357
  jsonlHealOk += 1;
15276
15358
  try {
15277
- streamLogPath.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-persist-heal] convId=${sessionId.slice(0, 8)} turnIndex=${i} role=${q.role} outcome=ok messageId=${(messageId ?? "").slice(0, 8)}
15359
+ streamLogPath.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-persist-heal] convId=${sessionId.slice(0, 8)} turnIndex=${i} role=${q2.role} outcome=ok messageId=${(messageId ?? "").slice(0, 8)}
15278
15360
  `);
15279
15361
  } catch {
15280
15362
  }
15281
15363
  } catch (err) {
15282
15364
  jsonlHealFail += 1;
15283
15365
  try {
15284
- streamLogPath.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-persist-heal] convId=${sessionId.slice(0, 8)} turnIndex=${i} role=${q.role} outcome=fail reason=${JSON.stringify((err instanceof Error ? err.message : String(err)).slice(0, 200))}
15366
+ streamLogPath.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-persist-heal] convId=${sessionId.slice(0, 8)} turnIndex=${i} role=${q2.role} outcome=fail reason=${JSON.stringify((err instanceof Error ? err.message : String(err)).slice(0, 200))}
15285
15367
  `);
15286
15368
  } catch {
15287
15369
  }
@@ -15606,10 +15688,10 @@ function computeSpecialistDomains(accountId) {
15606
15688
  const resolveDesc = (qualified) => {
15607
15689
  if (!qualified.startsWith("mcp__")) return void 0;
15608
15690
  const rest = qualified.slice(5);
15609
- const sep9 = rest.indexOf("__");
15610
- if (sep9 < 0) return void 0;
15611
- const plugin = rest.slice(0, sep9);
15612
- const tool = rest.slice(sep9 + 2);
15691
+ const sep10 = rest.indexOf("__");
15692
+ if (sep10 < 0) return void 0;
15693
+ const plugin = rest.slice(0, sep10);
15694
+ const tool = rest.slice(sep10 + 2);
15613
15695
  let map = descByPlugin.get(plugin);
15614
15696
  if (!map) {
15615
15697
  const dir = pluginDirs.get(plugin);
@@ -15912,18 +15994,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
15912
15994
  ]);
15913
15995
  var app21 = new Hono();
15914
15996
  app21.post("/", async (c) => {
15915
- const TAG53 = "[admin:events]";
15997
+ const TAG54 = "[admin:events]";
15916
15998
  let body;
15917
15999
  try {
15918
16000
  body = await c.req.json();
15919
16001
  } catch (err) {
15920
16002
  const detail = err instanceof Error ? err.message : String(err);
15921
- console.error(`${TAG53} reject reason=body-not-json detail=${detail}`);
16003
+ console.error(`${TAG54} reject reason=body-not-json detail=${detail}`);
15922
16004
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
15923
16005
  }
15924
16006
  const event = typeof body.event === "string" ? body.event : "";
15925
16007
  if (!ALLOWED_EVENTS.has(event)) {
15926
- console.error(`${TAG53} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
16008
+ console.error(`${TAG54} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
15927
16009
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
15928
16010
  }
15929
16011
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -18564,7 +18646,7 @@ var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "
18564
18646
  var app23 = new Hono();
18565
18647
  app23.get("/", requireAdminSession, async (c) => {
18566
18648
  const cacheKey = c.var.cacheKey;
18567
- const q = (c.req.query("q") ?? "").trim();
18649
+ const q2 = (c.req.query("q") ?? "").trim();
18568
18650
  const rawLimit = c.req.query("limit");
18569
18651
  const rawLabels = c.req.query("labels");
18570
18652
  const rawThreshold = c.req.query("threshold");
@@ -18573,13 +18655,13 @@ app23.get("/", requireAdminSession, async (c) => {
18573
18655
  console.error(`[graph-search] auth-rejected endpoint="/api/admin/graph-search" reason="no account for session"`);
18574
18656
  return c.json({ error: "Account not found for session" }, 401);
18575
18657
  }
18576
- if (!q) return c.json({ error: "q (search query) required" }, 400);
18658
+ if (!q2) return c.json({ error: "q (search query) required" }, 400);
18577
18659
  const labels = rawLabels ? rawLabels.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : [];
18578
18660
  const wildcard = labels.includes("*");
18579
18661
  const noChips = labels.length === 0;
18580
18662
  const allLabels = wildcard || noChips;
18581
18663
  if (noChips && !wildcard) {
18582
- console.error(`[graph-search] op=labels-default applied=all query="${q}"`);
18664
+ console.error(`[graph-search] op=labels-default applied=all query="${q2}"`);
18583
18665
  }
18584
18666
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
18585
18667
  const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, MAX_LIMIT) : DEFAULT_LIMIT;
@@ -18599,7 +18681,7 @@ app23.get("/", requireAdminSession, async (c) => {
18599
18681
  const session = getSession();
18600
18682
  try {
18601
18683
  const res = await hybrid(session, embed, {
18602
- query: q,
18684
+ query: q2,
18603
18685
  accountId,
18604
18686
  labels: forwardedLabels,
18605
18687
  limit,
@@ -18609,7 +18691,7 @@ app23.get("/", requireAdminSession, async (c) => {
18609
18691
  const total = Date.now() - started;
18610
18692
  if (res.embedError) {
18611
18693
  console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
18612
- console.error(`[graph-search] embed-degraded query="${q}" results=${res.results.length}`);
18694
+ console.error(`[graph-search] embed-degraded query="${q2}" results=${res.results.length}`);
18613
18695
  }
18614
18696
  let labelMatchCount = 0;
18615
18697
  let bodyFulltextCount = 0;
@@ -18660,16 +18742,16 @@ app23.get("/", requireAdminSession, async (c) => {
18660
18742
  }
18661
18743
  resolvedResults = Array.from(seen.values()).sort((a, b) => b.score - a.score);
18662
18744
  console.error(
18663
- `[graph-search] label-match query="${q}" hits=${labelMatchCount}`
18745
+ `[graph-search] label-match query="${q2}" hits=${labelMatchCount}`
18664
18746
  );
18665
18747
  console.error(
18666
- `[graph-search] body-fulltext query="${q}" hits=${bodyFulltextCount} ms=${total}`
18748
+ `[graph-search] body-fulltext query="${q2}" hits=${bodyFulltextCount} ms=${total}`
18667
18749
  );
18668
18750
  }
18669
18751
  const labelsToken = wildcard ? "*" : noChips ? "all" : labels.join(",");
18670
18752
  const expandedFlag = !wildcard && wantsBodyFulltext ? 1 : 0;
18671
18753
  console.error(
18672
- `[graph-search] query="${q}" labels=${labelsToken} expanded=${expandedFlag} limit=${limit} mode=${res.mode} raw-results=${res.results.length} resolved-results=${resolvedResults.length} expand-ms=${res.expandMs} total-ms=${total}`
18754
+ `[graph-search] query="${q2}" labels=${labelsToken} expanded=${expandedFlag} limit=${limit} mode=${res.mode} raw-results=${res.results.length} resolved-results=${resolvedResults.length} expand-ms=${res.expandMs} total-ms=${total}`
18673
18755
  );
18674
18756
  console.error(
18675
18757
  `[graph-search] threshold=${res.threshold ?? "off"} raw-merged=${res.rawMerged} suppressed=${res.suppressed} rendered=${res.results.length} bm25-bypass=${res.bm25Bypass}`
@@ -18692,7 +18774,7 @@ app23.get("/", requireAdminSession, async (c) => {
18692
18774
  } catch (err) {
18693
18775
  const elapsed = Date.now() - started;
18694
18776
  const message = err instanceof Error ? err.message : String(err);
18695
- console.error(`[graph-search] neo4j-unreachable query="${q}" ms=${elapsed} err="${message}"`);
18777
+ console.error(`[graph-search] neo4j-unreachable query="${q2}" ms=${elapsed} err="${message}"`);
18696
18778
  return c.json({ error: `Graph search unavailable: ${message}` }, 503);
18697
18779
  } finally {
18698
18780
  await session.close();
@@ -18759,7 +18841,11 @@ var FILTER_TOP_LEVEL_LABELS = Object.freeze(
18759
18841
  "Risk",
18760
18842
  "Source",
18761
18843
  "Finding",
18762
- "Hypothesis"
18844
+ "Hypothesis",
18845
+ // Base-schema ledger (schema-base.md, Task 1819). A cash movement
18846
+ // attached to no invoice — its only edges are outbound, so nothing
18847
+ // drills down to it and a chip is the sole way to reach it.
18848
+ "CashEntry"
18763
18849
  ])
18764
18850
  );
18765
18851
  var AGENT_ACTION_LABELS = Object.freeze(
@@ -18810,6 +18896,17 @@ var STATIC_TOP_LEVEL_LABELS = Object.freeze(
18810
18896
  "Review",
18811
18897
  "ImageObject",
18812
18898
  "Invoice",
18899
+ // Supplier invoice. Declared in schema-base.md's `## Node Types` table
18900
+ // (promoted there from schema-construction.md's top-level table by
18901
+ // 7b225f814), which this module never parses — so it is static, not
18902
+ // derived. Top-level because it is operator-entry with a natural key
18903
+ // (accountId, supplier, confirmationNumber), unlike its InvoiceLine /
18904
+ // InvoicePayment / Credit children.
18905
+ "InboundInvoice",
18906
+ // Ledger cash movement, declared in schema-base.md. Operator-entry with
18907
+ // a natural key (accountId, entryId) and no parent label, so it is
18908
+ // reachable only as a chip.
18909
+ "CashEntry",
18813
18910
  "Task",
18814
18911
  "Project",
18815
18912
  "Event",
@@ -18860,8 +18957,8 @@ function parseTopLevelLabels(content) {
18860
18957
  const line = lines[i];
18861
18958
  if (line.startsWith("## ")) return [];
18862
18959
  if (!line.includes("|")) continue;
18863
- const sep9 = lines[i + 1];
18864
- if (!/^\s*\|?[\s:|-]+\|?\s*$/.test(sep9) || !sep9.includes("-")) continue;
18960
+ const sep10 = lines[i + 1];
18961
+ if (!/^\s*\|?[\s:|-]+\|?\s*$/.test(sep10) || !sep10.includes("-")) continue;
18865
18962
  const headers = splitPipeRow(line).map((h) => stripBackticks(h).toLowerCase());
18866
18963
  const labelCol = headers.findIndex((h) => h === "neo4j label");
18867
18964
  if (labelCol === -1) return [];
@@ -19112,7 +19209,7 @@ async function handleNeighbourhood(c, accountId) {
19112
19209
  }
19113
19210
  const includeAgentActions = c.req.query("includeAgentActions") === "1";
19114
19211
  const agentActionLabels = includeAgentActions ? [] : [...AGENT_ACTION_LABELS];
19115
- const q = (c.req.query("q") ?? "").trim();
19212
+ const q2 = (c.req.query("q") ?? "").trim();
19116
19213
  const rawLimit = c.req.query("limit");
19117
19214
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : NEIGHBOURHOOD_SEARCH_DEFAULT_LIMIT;
19118
19215
  const searchLimit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, NEIGHBOURHOOD_SEARCH_MAX_LIMIT) : NEIGHBOURHOOD_SEARCH_DEFAULT_LIMIT;
@@ -19120,9 +19217,9 @@ async function handleNeighbourhood(c, accountId) {
19120
19217
  const session = getSession();
19121
19218
  try {
19122
19219
  let allowedIds = null;
19123
- if (q) {
19220
+ if (q2) {
19124
19221
  const searchRes = await hybrid(session, embed, {
19125
- query: q,
19222
+ query: q2,
19126
19223
  accountId,
19127
19224
  labels: [],
19128
19225
  limit: searchLimit,
@@ -19205,7 +19302,7 @@ async function handleNeighbourhood(c, accountId) {
19205
19302
  }
19206
19303
  if (allowedIds !== null) {
19207
19304
  console.error(
19208
- `[graph-page] neighbourhood-search-intersect q="${q}" allowed=${allowedIds.length} root=${elementId} rendered=${nodes.length}`
19305
+ `[graph-page] neighbourhood-search-intersect q="${q2}" allowed=${allowedIds.length} root=${elementId} rendered=${nodes.length}`
19209
19306
  );
19210
19307
  }
19211
19308
  return c.json({ nodes, edges });
@@ -20546,18 +20643,68 @@ var session_rc_spawn_default = app35;
20546
20643
 
20547
20644
  // server/routes/admin/session-reseat.ts
20548
20645
  import { randomUUID as randomUUID12 } from "crypto";
20549
- function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticatedName) {
20646
+
20647
+ // server/channel-session-override.ts
20648
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync12, renameSync as renameSync9 } from "fs";
20649
+ import { join as join35 } from "path";
20650
+ var FILE2 = "channel-session-override.json";
20651
+ var UUID2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
20652
+ function channelOverridePath(accountDir) {
20653
+ return join35(accountDir, FILE2);
20654
+ }
20655
+ function senderKey(channel, senderId) {
20656
+ return `${channel}:${senderId}`;
20657
+ }
20658
+ function readRaw2(accountDir) {
20659
+ try {
20660
+ return JSON.parse(readFileSync34(channelOverridePath(accountDir), "utf8"));
20661
+ } catch {
20662
+ return null;
20663
+ }
20664
+ }
20665
+ function readChannelOverrideId(accountDir, channel, senderId) {
20666
+ const raw = readRaw2(accountDir);
20667
+ if (!raw || !raw.bySender || typeof raw.bySender !== "object") return null;
20668
+ const v = raw.bySender[senderKey(channel, senderId)];
20669
+ return typeof v === "string" && UUID2.test(v) ? v : null;
20670
+ }
20671
+ function writeChannelOverrideId(accountDir, channel, senderId, sessionId) {
20672
+ const raw = readRaw2(accountDir);
20673
+ const bySender = {};
20674
+ if (raw?.bySender && typeof raw.bySender === "object") {
20675
+ for (const [k, v] of Object.entries(raw.bySender)) {
20676
+ if (typeof v === "string") bySender[k] = v;
20677
+ }
20678
+ }
20679
+ bySender[senderKey(channel, senderId)] = sessionId;
20680
+ const path3 = channelOverridePath(accountDir);
20681
+ const tmp = `${path3}.${process.pid}.tmp`;
20682
+ writeFileSync12(tmp, JSON.stringify({ bySender }, null, 2), "utf8");
20683
+ renameSync9(tmp, path3);
20684
+ }
20685
+
20686
+ // server/routes/admin/session-reseat.ts
20687
+ function isAdminChannelSource(source) {
20688
+ return !!source && source.role === "admin" && (source.channel === "whatsapp" || source.channel === "telegram") && typeof source.senderId === "string" && source.senderId.length > 0;
20689
+ }
20690
+ function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticatedName, source) {
20550
20691
  const sessionId = mintId();
20551
20692
  const key = `session:${sessionId}`;
20552
20693
  const payload = {
20553
20694
  sessionId,
20554
20695
  forkFromSessionId: body.fromSessionId,
20555
20696
  model: body.model,
20556
- role: "admin",
20557
- channel: "webchat",
20558
- senderId: key,
20559
- webchatChannel: webchatChannelDescriptor(key)
20697
+ role: "admin"
20560
20698
  };
20699
+ if (isAdminChannelSource(source)) {
20700
+ payload.channel = source.channel;
20701
+ payload.senderId = source.senderId;
20702
+ if (source.accountId) payload.targetAccountId = source.accountId;
20703
+ } else {
20704
+ payload.channel = "webchat";
20705
+ payload.senderId = key;
20706
+ payload.webchatChannel = webchatChannelDescriptor(key);
20707
+ }
20561
20708
  if (body.name) payload.name = body.name;
20562
20709
  if (body.permissionMode) payload.permissionMode = body.permissionMode;
20563
20710
  if (body.effort) payload.effort = body.effort;
@@ -20598,14 +20745,18 @@ app36.post("/", requireAdminSession, async (c) => {
20598
20745
  }
20599
20746
  const accountId = cacheKey ? getAccountIdForSession(cacheKey) : void 0;
20600
20747
  const authenticatedName = accountId ? await resolveAuthenticatedName(accountId, adminUserId) : void 0;
20748
+ const sourceMeta = readSourceRowMeta(fromSessionId);
20749
+ const source = sourceMeta ? { channel: sourceMeta.channel, senderId: sourceMeta.senderId, role: sourceMeta.role, accountId: sourceMeta.accountId } : null;
20601
20750
  const plan = resolveReseatPlan(
20602
20751
  { fromSessionId, model, ...permissionMode ? { permissionMode } : {}, ...effort ? { effort } : {} },
20603
20752
  randomUUID12,
20604
20753
  adminUserId,
20605
- authenticatedName
20754
+ authenticatedName,
20755
+ source
20606
20756
  );
20757
+ const isChannelFork = isAdminChannelSource(source);
20607
20758
  const reseatReqId = randomUUID12();
20608
- console.log(`[chat-reseat] op=request reseatReqId=${reseatReqId} from=${fromSessionId} to-model=${model} to-mode=${permissionMode ?? "none"} to-effort=${effort ?? "none"} new=${plan.sessionId}`);
20759
+ console.log(`[chat-reseat] op=request reseatReqId=${reseatReqId} from=${fromSessionId} from-channel=${source?.channel ?? "none"} from-sender=${source?.senderId ?? "none"} to-model=${model} to-mode=${permissionMode ?? "none"} to-effort=${effort ?? "none"} new=${plan.sessionId}`);
20609
20760
  let res;
20610
20761
  try {
20611
20762
  res = await fetch(`${managerBase("session-reseat:wrapper")}/rc-spawn`, {
@@ -20645,6 +20796,23 @@ app36.post("/", requireAdminSession, async (c) => {
20645
20796
  } catch (err) {
20646
20797
  console.error(`[session-reseat] canonical-forward-failed err=${err instanceof Error ? err.message : String(err)}`);
20647
20798
  }
20799
+ if (isChannelFork) {
20800
+ const channel = plan.payload.channel;
20801
+ const senderId = plan.payload.senderId;
20802
+ try {
20803
+ const provenance = source?.accountId ? "source-account" : "boot-account-fallback";
20804
+ const dir = source?.accountId ? listValidAccounts().find((a) => a.accountId === source.accountId)?.accountDir ?? null : resolveAccount()?.accountDir ?? null;
20805
+ if (dir) {
20806
+ writeChannelOverrideId(dir, channel, senderId, plan.sessionId);
20807
+ console.log(`[chat-reseat] op=channel-forward reseatReqId=${reseatReqId} senderId=${senderId} from=${fromSessionId} to=${plan.sessionId} keyedOn=${provenance}`);
20808
+ } else {
20809
+ console.error(`[session-reseat] channel-forward-failed reason=no-account-dir accountId=${source?.accountId ?? "none"} senderId=${senderId}`);
20810
+ }
20811
+ } catch (err) {
20812
+ console.error(`[session-reseat] channel-forward-failed err=${err instanceof Error ? err.message : String(err)} senderId=${senderId}`);
20813
+ }
20814
+ console.log(`[chat-reseat] op=channel-fork-unbound reseatReqId=${reseatReqId} senderId=${senderId} session=${plan.sessionId} note=binds-on-next-inbound`);
20815
+ }
20648
20816
  const target = `/chat?session=${plan.sessionId}`;
20649
20817
  console.log(`[chat-reseat] op=acquired reseatReqId=${reseatReqId} pid=${parsed?.spawnedPid ?? "none"} new=${plan.sessionId}`);
20650
20818
  console.log(`[chat-reseat] op=navigate reseatReqId=${reseatReqId} target=${target}`);
@@ -20779,14 +20947,14 @@ app37.get("/", async (c) => {
20779
20947
  var system_stats_default = app37;
20780
20948
 
20781
20949
  // server/routes/admin/health.ts
20782
- import { existsSync as existsSync31, readFileSync as readFileSync34 } from "fs";
20783
- import { resolve as resolve26, join as join35 } from "path";
20950
+ import { existsSync as existsSync31, readFileSync as readFileSync35 } from "fs";
20951
+ import { resolve as resolve26, join as join36 } from "path";
20784
20952
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
20785
20953
  var brandHostname = "maxy";
20786
- var brandJsonPath = join35(PLATFORM_ROOT6, "config", "brand.json");
20954
+ var brandJsonPath = join36(PLATFORM_ROOT6, "config", "brand.json");
20787
20955
  if (existsSync31(brandJsonPath)) {
20788
20956
  try {
20789
- const brand = JSON.parse(readFileSync34(brandJsonPath, "utf-8"));
20957
+ const brand = JSON.parse(readFileSync35(brandJsonPath, "utf-8"));
20790
20958
  if (brand.hostname) brandHostname = brand.hostname;
20791
20959
  } catch {
20792
20960
  }
@@ -20796,7 +20964,7 @@ var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
20796
20964
  var PROBE_TIMEOUT_MS = 1e3;
20797
20965
  function readVersion() {
20798
20966
  if (!existsSync31(VERSION_FILE)) return "unknown";
20799
- return readFileSync34(VERSION_FILE, "utf-8").trim() || "unknown";
20967
+ return readFileSync35(VERSION_FILE, "utf-8").trim() || "unknown";
20800
20968
  }
20801
20969
  async function probeConversationDb() {
20802
20970
  let session;
@@ -20976,7 +21144,7 @@ function managerLogFollowUrl(sessionId, opts) {
20976
21144
 
20977
21145
  // server/routes/admin/linkedin-ingest.ts
20978
21146
  var TAG31 = "[linkedin-ingest-route]";
20979
- var UUID2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
21147
+ var UUID3 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
20980
21148
  var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
20981
21149
  var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
20982
21150
  function rejectSchema(reason, missing = []) {
@@ -20990,7 +21158,7 @@ function validate(body) {
20990
21158
  if (b.kind !== "profile" && b.kind !== "thread") missing.push("kind");
20991
21159
  if (typeof b.capturedAt !== "string" || !ISO.test(b.capturedAt)) missing.push("capturedAt");
20992
21160
  if (typeof b.pageUrl !== "string" || !LINKEDIN_URL.test(b.pageUrl)) missing.push("pageUrl");
20993
- if (typeof b.dispatchId !== "string" || !UUID2.test(b.dispatchId)) missing.push("dispatchId");
21161
+ if (typeof b.dispatchId !== "string" || !UUID3.test(b.dispatchId)) missing.push("dispatchId");
20994
21162
  if (missing.length) return rejectSchema("envelope", missing);
20995
21163
  if (b.kind === "profile") {
20996
21164
  const p = b.profile;
@@ -21573,7 +21741,7 @@ var browser_default = app45;
21573
21741
 
21574
21742
  // server/routes/admin/calendar.ts
21575
21743
  import { existsSync as existsSync32 } from "fs";
21576
- import { join as join37 } from "path";
21744
+ import { join as join38 } from "path";
21577
21745
 
21578
21746
  // server/lib/calendar-ics.ts
21579
21747
  var CRLF = "\r\n";
@@ -21672,8 +21840,8 @@ function countVEvents(ics) {
21672
21840
  }
21673
21841
 
21674
21842
  // server/lib/calendar-availability.ts
21675
- import { readFileSync as readFileSync35 } from "fs";
21676
- import { join as join36 } from "path";
21843
+ import { readFileSync as readFileSync36 } from "fs";
21844
+ import { join as join37 } from "path";
21677
21845
  var AVAILABILITY_FILENAME = "calendar-availability.json";
21678
21846
  var REQUIRED_KEYS = [
21679
21847
  "timezone",
@@ -21682,10 +21850,10 @@ var REQUIRED_KEYS = [
21682
21850
  "weekly"
21683
21851
  ];
21684
21852
  function readAvailabilityConfig(accountDir) {
21685
- const path3 = join36(accountDir, AVAILABILITY_FILENAME);
21853
+ const path3 = join37(accountDir, AVAILABILITY_FILENAME);
21686
21854
  let raw;
21687
21855
  try {
21688
- raw = readFileSync35(path3, "utf-8");
21856
+ raw = readFileSync36(path3, "utf-8");
21689
21857
  } catch {
21690
21858
  throw new Error(`availability not configured: ${path3} not found`);
21691
21859
  }
@@ -22044,7 +22212,7 @@ app46.get("/booking-link", requireAdminSession, (c) => {
22044
22212
  console.error('[calendar] op=booking-link-fail err="no account configured"');
22045
22213
  return c.json({ bookingDomain: null });
22046
22214
  }
22047
- if (!existsSync32(join37(account.accountDir, AVAILABILITY_FILENAME))) {
22215
+ if (!existsSync32(join38(account.accountDir, AVAILABILITY_FILENAME))) {
22048
22216
  console.log("[calendar] op=booking-link bookingDomain=none source=availability-config");
22049
22217
  return c.json({ bookingDomain: null });
22050
22218
  }
@@ -22847,7 +23015,7 @@ app56.route("/request-magic-link", request_magic_link_default);
22847
23015
  var access_default = app56;
22848
23016
 
22849
23017
  // server/routes/sites.ts
22850
- import { existsSync as existsSync33, readFileSync as readFileSync36, realpathSync as realpathSync7, statSync as statSync14 } from "fs";
23018
+ import { existsSync as existsSync33, readFileSync as readFileSync37, realpathSync as realpathSync7, statSync as statSync14 } from "fs";
22851
23019
  import { resolve as resolve28 } from "path";
22852
23020
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
22853
23021
  var MIME = {
@@ -22911,13 +23079,13 @@ app57.get("/:rel{.*}", (c) => {
22911
23079
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
22912
23080
  return c.text("Forbidden", 403);
22913
23081
  }
22914
- let stat8;
23082
+ let stat9;
22915
23083
  try {
22916
- stat8 = existsSync33(filePath) ? statSync14(filePath) : null;
23084
+ stat9 = existsSync33(filePath) ? statSync14(filePath) : null;
22917
23085
  } catch {
22918
- stat8 = null;
23086
+ stat9 = null;
22919
23087
  }
22920
- if (stat8?.isDirectory() && !reqPath.endsWith("/")) {
23088
+ if (stat9?.isDirectory() && !reqPath.endsWith("/")) {
22921
23089
  const search = new URL(c.req.url).search;
22922
23090
  const target = `${reqPath}/${search}`;
22923
23091
  console.log(
@@ -22925,7 +23093,7 @@ app57.get("/:rel{.*}", (c) => {
22925
23093
  );
22926
23094
  return c.redirect(target, 301);
22927
23095
  }
22928
- if (stat8?.isDirectory()) {
23096
+ if (stat9?.isDirectory()) {
22929
23097
  filePath = resolve28(filePath, "index.html");
22930
23098
  }
22931
23099
  if (!filePath.startsWith(rootDir + "/")) {
@@ -22951,7 +23119,7 @@ app57.get("/:rel{.*}", (c) => {
22951
23119
  }
22952
23120
  let body;
22953
23121
  try {
22954
- body = readFileSync36(realPath);
23122
+ body = readFileSync37(realPath);
22955
23123
  } catch (err) {
22956
23124
  const code = err?.code;
22957
23125
  if (code === "EISDIR") {
@@ -22987,7 +23155,7 @@ var sites_default = app57;
22987
23155
 
22988
23156
  // app/lib/visitor-token.ts
22989
23157
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
22990
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync37, writeFileSync as writeFileSync12 } from "fs";
23158
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync38, writeFileSync as writeFileSync13 } from "fs";
22991
23159
  import { dirname as dirname12 } from "path";
22992
23160
  var TOKEN_PREFIX = "v1.";
22993
23161
  var SECRET_BYTES = 32;
@@ -22996,7 +23164,7 @@ var cachedSecret = null;
22996
23164
  function getSecret() {
22997
23165
  if (cachedSecret) return cachedSecret;
22998
23166
  try {
22999
- const hex2 = readFileSync37(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23167
+ const hex2 = readFileSync38(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23000
23168
  if (hex2.length === SECRET_BYTES * 2) {
23001
23169
  cachedSecret = Buffer.from(hex2, "hex");
23002
23170
  return cachedSecret;
@@ -23006,11 +23174,11 @@ function getSecret() {
23006
23174
  const fresh = randomBytes(SECRET_BYTES).toString("hex");
23007
23175
  try {
23008
23176
  mkdirSync8(dirname12(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
23009
- writeFileSync12(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
23177
+ writeFileSync13(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
23010
23178
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
23011
23179
  } catch {
23012
23180
  }
23013
- const hex = readFileSync37(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23181
+ const hex = readFileSync38(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23014
23182
  cachedSecret = Buffer.from(hex, "hex");
23015
23183
  return cachedSecret;
23016
23184
  }
@@ -23063,8 +23231,8 @@ var VISITOR_COOKIE_NAME = "mxy_v";
23063
23231
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
23064
23232
 
23065
23233
  // app/lib/brand-config.ts
23066
- import { existsSync as existsSync34, readFileSync as readFileSync38 } from "fs";
23067
- import { join as join38 } from "path";
23234
+ import { existsSync as existsSync34, readFileSync as readFileSync39 } from "fs";
23235
+ import { join as join39 } from "path";
23068
23236
  var cached2 = null;
23069
23237
  var cachedAttempted = false;
23070
23238
  function readBrandConfig() {
@@ -23072,10 +23240,10 @@ function readBrandConfig() {
23072
23240
  cachedAttempted = true;
23073
23241
  const platformRoot5 = process.env.MAXY_PLATFORM_ROOT;
23074
23242
  if (!platformRoot5) return null;
23075
- const brandPath = join38(platformRoot5, "config", "brand.json");
23243
+ const brandPath = join39(platformRoot5, "config", "brand.json");
23076
23244
  if (!existsSync34(brandPath)) return null;
23077
23245
  try {
23078
- cached2 = JSON.parse(readFileSync38(brandPath, "utf-8"));
23246
+ cached2 = JSON.parse(readFileSync39(brandPath, "utf-8"));
23079
23247
  return cached2;
23080
23248
  } catch {
23081
23249
  return null;
@@ -23209,8 +23377,8 @@ function appendConsentParams(pageUrl, token) {
23209
23377
  const hashIdx = pageUrl.indexOf("#");
23210
23378
  const hash = hashIdx >= 0 ? pageUrl.slice(hashIdx) : "";
23211
23379
  const base = hashIdx >= 0 ? pageUrl.slice(0, hashIdx) : pageUrl;
23212
- const sep9 = base.indexOf("?") >= 0 ? "&" : "?";
23213
- return base + sep9 + `consent=needed&v=${encodeURIComponent(token)}` + hash;
23380
+ const sep10 = base.indexOf("?") >= 0 ? "&" : "?";
23381
+ return base + sep10 + `consent=needed&v=${encodeURIComponent(token)}` + hash;
23214
23382
  }
23215
23383
  }
23216
23384
  var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
@@ -23573,13 +23741,13 @@ var visitor_event_default = app60;
23573
23741
 
23574
23742
  // server/routes/session.ts
23575
23743
  import { resolve as resolve29 } from "path";
23576
- import { existsSync as existsSync35, writeFileSync as writeFileSync13, mkdirSync as mkdirSync9 } from "fs";
23744
+ import { existsSync as existsSync35, writeFileSync as writeFileSync14, mkdirSync as mkdirSync9 } from "fs";
23577
23745
  var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
23578
23746
  function writeBrandingCache(accountId, agentSlug, branding) {
23579
23747
  try {
23580
23748
  const cacheDir = resolve29(MAXY_DIR, "branding-cache", accountId);
23581
23749
  mkdirSync9(cacheDir, { recursive: true });
23582
- writeFileSync13(resolve29(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
23750
+ writeFileSync14(resolve29(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
23583
23751
  } catch (err) {
23584
23752
  console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
23585
23753
  }
@@ -23904,8 +24072,205 @@ app62.get("/free-busy", async (c) => {
23904
24072
  });
23905
24073
  var calendar_public_default = app62;
23906
24074
 
24075
+ // server/routes/portal-fetch.ts
24076
+ import { createReadStream as createReadStream3 } from "fs";
24077
+ import { stat as stat6, readFile as readFile6, realpath } from "fs/promises";
24078
+ import { join as join40, resolve as resolve30, sep as sep8 } from "path";
24079
+ import { Readable as Readable3 } from "stream";
24080
+
24081
+ // ../plugins/cloudflare/bin/schema-exposed-dirs.mjs
24082
+ var ONT_START = "<!-- ontology-buckets:start -->";
24083
+ var ONT_END = "<!-- ontology-buckets:end -->";
24084
+ var OWNED_START = "<!-- plugin-owned-dirs:start -->";
24085
+ var OWNED_END = "<!-- plugin-owned-dirs:end -->";
24086
+ var ALWAYS_EXPOSED = "output";
24087
+ var DOMAIN_LINE = /^- `([^`/]+)\/` - one folder per /;
24088
+ var OWNED_LINE = /^- `([^`/]+)\/` — /;
24089
+ function region(text, start, end) {
24090
+ const i = text.indexOf(start);
24091
+ if (i === -1) return null;
24092
+ const j = text.indexOf(end, i);
24093
+ return text.slice(i, j === -1 ? text.length : j);
24094
+ }
24095
+ function allowedBlockOrNull(text) {
24096
+ const lines = text.split("\n");
24097
+ const start = lines.findIndex((l) => l.trim() === "```allowed-top-level");
24098
+ if (start === -1) return null;
24099
+ for (let j = start + 1; j < lines.length; j++) {
24100
+ if (lines[j].trim() === "```") {
24101
+ return lines.slice(start + 1, j).map((l) => l.trim()).filter(Boolean);
24102
+ }
24103
+ }
24104
+ return null;
24105
+ }
24106
+ function resolveExposedDirs(schemaText) {
24107
+ const empty = {
24108
+ schemaPresent: false,
24109
+ allowed: (
24110
+ /** @type {string[]} */
24111
+ []
24112
+ ),
24113
+ domainBuckets: (
24114
+ /** @type {string[]} */
24115
+ []
24116
+ ),
24117
+ pluginOwned: (
24118
+ /** @type {string[]} */
24119
+ []
24120
+ ),
24121
+ collisions: (
24122
+ /** @type {string[]} */
24123
+ []
24124
+ ),
24125
+ exposed: (
24126
+ /** @type {string[]} */
24127
+ []
24128
+ )
24129
+ };
24130
+ if (typeof schemaText !== "string" || schemaText.length === 0) return empty;
24131
+ const allowed = allowedBlockOrNull(schemaText);
24132
+ if (allowed === null) return { ...empty, schemaPresent: true };
24133
+ const ont = region(schemaText, ONT_START, ONT_END);
24134
+ const owned = region(schemaText, OWNED_START, OWNED_END);
24135
+ const dirsIn = (text, re) => {
24136
+ if (!text) return [];
24137
+ const out = [];
24138
+ for (const line of text.split("\n")) {
24139
+ const m = re.exec(line.trim());
24140
+ if (m && !out.includes(m[1])) out.push(m[1]);
24141
+ }
24142
+ return out;
24143
+ };
24144
+ const domainBuckets = dirsIn(ont, DOMAIN_LINE);
24145
+ const pluginOwned = dirsIn(owned, OWNED_LINE);
24146
+ const collisions = domainBuckets.filter((d) => pluginOwned.includes(d));
24147
+ const candidates = [ALWAYS_EXPOSED, ...domainBuckets.filter((d) => !collisions.includes(d))];
24148
+ const exposed = [...new Set(candidates)].filter((d) => allowed.includes(d)).sort();
24149
+ return { schemaPresent: true, allowed, domainBuckets, pluginOwned, collisions, exposed };
24150
+ }
24151
+
24152
+ // server/routes/portal-fetch.ts
24153
+ var app63 = new Hono();
24154
+ var TAG45 = "[portal-fetch]";
24155
+ async function verifySignature(secret, accountId, relPath, expiresAtMs, signature, nowMs) {
24156
+ if (!secret || !signature) return false;
24157
+ if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs) return false;
24158
+ const key = await crypto.subtle.importKey(
24159
+ "raw",
24160
+ new TextEncoder().encode(secret),
24161
+ { name: "HMAC", hash: "SHA-256" },
24162
+ false,
24163
+ ["sign"]
24164
+ );
24165
+ const mac = await crypto.subtle.sign(
24166
+ "HMAC",
24167
+ key,
24168
+ new TextEncoder().encode(`${accountId}
24169
+ ${relPath}
24170
+ ${expiresAtMs}`)
24171
+ );
24172
+ const expected = [...new Uint8Array(mac)].map((b) => b.toString(16).padStart(2, "0")).join("");
24173
+ if (expected.length !== signature.length) return false;
24174
+ let diff = 0;
24175
+ for (let i = 0; i < expected.length; i++) diff |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
24176
+ return diff === 0;
24177
+ }
24178
+ async function readAccountSecret(accountId) {
24179
+ const path3 = resolve30(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
24180
+ let text;
24181
+ try {
24182
+ text = await readFile6(path3, "utf8");
24183
+ } catch {
24184
+ console.error(`${TAG45} op=no-secret account="${accountId}" path="${path3}"`);
24185
+ return "";
24186
+ }
24187
+ for (const line of text.split("\n")) {
24188
+ const m = line.match(/^PORTAL_FETCH_SECRET=(.*)$/);
24189
+ if (m) return m[1].trim();
24190
+ }
24191
+ console.error(`${TAG45} op=no-secret account="${accountId}" reason=key-absent path="${path3}"`);
24192
+ return "";
24193
+ }
24194
+ function isValidAccountId2(value) {
24195
+ return /^[A-Za-z0-9-]+$/.test(value);
24196
+ }
24197
+ async function resolveFetchTarget(accountDir, relPath) {
24198
+ let schemaText = null;
24199
+ try {
24200
+ schemaText = await readFile6(join40(accountDir, "SCHEMA.md"), "utf8");
24201
+ } catch {
24202
+ schemaText = null;
24203
+ }
24204
+ const { exposed } = resolveExposedDirs(schemaText);
24205
+ if (exposed.length === 0) return { ok: false, reason: "not-exposed" };
24206
+ const lexical = resolve30(accountDir, relPath);
24207
+ let root;
24208
+ let absolute;
24209
+ try {
24210
+ root = await realpath(accountDir);
24211
+ absolute = await realpath(lexical);
24212
+ } catch {
24213
+ return { ok: false, reason: "enoent" };
24214
+ }
24215
+ if (absolute === root || !absolute.startsWith(root + sep8)) return { ok: false, reason: "not-exposed" };
24216
+ const top = absolute.slice(root.length + 1).split(sep8)[0];
24217
+ if (!exposed.includes(top)) return { ok: false, reason: "not-exposed" };
24218
+ try {
24219
+ const st = await stat6(absolute);
24220
+ if (!st.isFile()) return { ok: false, reason: "enoent" };
24221
+ return { ok: true, absolute, sizeBytes: st.size };
24222
+ } catch {
24223
+ return { ok: false, reason: "enoent" };
24224
+ }
24225
+ }
24226
+ function q(value) {
24227
+ return `"${value.replace(/[\\"]/g, "\\$&").replace(/[\r\n\t]/g, " ")}"`;
24228
+ }
24229
+ async function handle(c, headOnly) {
24230
+ const accountId = c.req.query("accountId") ?? "";
24231
+ const relPath = c.req.query("path") ?? "";
24232
+ const exp = Number(c.req.query("exp") ?? "0");
24233
+ const sig = c.req.query("sig") ?? "";
24234
+ console.log(`${TAG45} op=request account=${q(accountId)} path=${q(relPath)} head=${headOnly}`);
24235
+ if (!isValidAccountId2(accountId)) {
24236
+ console.error(`${TAG45} op=denied account=${q(accountId)} reason=account-id`);
24237
+ return c.json({ ok: false, error: "denied" }, 401);
24238
+ }
24239
+ const secret = await readAccountSecret(accountId);
24240
+ if (!await verifySignature(secret, accountId, relPath, exp, sig, Date.now())) {
24241
+ console.error(`${TAG45} op=denied account=${q(accountId)} reason=secret`);
24242
+ return c.json({ ok: false, error: "denied" }, 401);
24243
+ }
24244
+ const target = await resolveFetchTarget(resolve30(DATA_ROOT, "accounts", accountId), relPath);
24245
+ if (!target.ok) {
24246
+ const op = target.reason === "enoent" ? "miss" : "denied";
24247
+ console.error(`${TAG45} op=${op} account=${accountId} reason=${target.reason}`);
24248
+ return c.json({ ok: false, error: "not found" }, 404);
24249
+ }
24250
+ if (headOnly) {
24251
+ console.log(`${TAG45} op=served account=${accountId} bytes=${target.sizeBytes} head=true`);
24252
+ return c.body(null, 200);
24253
+ }
24254
+ console.log(`${TAG45} op=served account=${accountId} bytes=${target.sizeBytes}`);
24255
+ return new Response(Readable3.toWeb(createReadStream3(target.absolute)), {
24256
+ status: 200,
24257
+ headers: {
24258
+ "content-type": detectMimeType(target.absolute),
24259
+ // Percent-encoded per RFC 6266, so a name carrying a quote cannot break
24260
+ // out of the form and CR/LF cannot reach the Headers constructor.
24261
+ "content-disposition": `attachment; filename*=UTF-8''${encodeURIComponent(relPath.split("/").pop() ?? "file")}`,
24262
+ "x-content-type-options": "nosniff",
24263
+ // Private client documents: never let a browser or proxy cache one.
24264
+ "cache-control": "private, no-store"
24265
+ }
24266
+ });
24267
+ }
24268
+ app63.get("/fetch", (c) => handle(c, false));
24269
+ app63.on("HEAD", "/fetch", (c) => handle(c, true));
24270
+ var portal_fetch_default = app63;
24271
+
23907
24272
  // app/lib/timeentry-census.ts
23908
- var TAG45 = "[timeentry-census]";
24273
+ var TAG46 = "[timeentry-census]";
23909
24274
  function reconcileTimeEntries(rows, now, horizonMs) {
23910
24275
  const openEntries = rows.openEntries.length;
23911
24276
  let oldestOpenAgeMs = 0;
@@ -23945,12 +24310,12 @@ async function runTimeEntryCensusOnce(session, now, horizonMs) {
23945
24310
  sumAdjustments: toNum(r.get("sumAdjustments"))
23946
24311
  }));
23947
24312
  const finding = reconcileTimeEntries({ openEntries, taskSums }, now, horizonMs);
23948
- const line = `${TAG45} openEntries=${finding.openEntries} oldestOpenAgeMin=${finding.oldestOpenAgeMin} maxOpenPerAccount=${finding.maxOpenPerAccount} driftTasks=${finding.driftTasks}`;
24313
+ const line = `${TAG46} openEntries=${finding.openEntries} oldestOpenAgeMin=${finding.oldestOpenAgeMin} maxOpenPerAccount=${finding.maxOpenPerAccount} driftTasks=${finding.driftTasks}`;
23949
24314
  if (finding.alarm) console.error(`${line} alarm=true`);
23950
24315
  else console.log(line);
23951
24316
  return finding;
23952
24317
  } catch (err) {
23953
- console.error(`${TAG45} error="${err instanceof Error ? err.message : String(err)}"`);
24318
+ console.error(`${TAG46} error="${err instanceof Error ? err.message : String(err)}"`);
23954
24319
  return null;
23955
24320
  }
23956
24321
  }
@@ -23964,13 +24329,13 @@ function startTimeEntryCensus(openSession, opts = {}) {
23964
24329
  try {
23965
24330
  session = openSession();
23966
24331
  } catch (err) {
23967
- console.error(`${TAG45} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`);
24332
+ console.error(`${TAG46} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`);
23968
24333
  return;
23969
24334
  }
23970
24335
  try {
23971
24336
  await runTimeEntryCensusOnce(session, Date.now(), horizonMs);
23972
24337
  } catch (err) {
23973
- console.error(`${TAG45} tick-failed error="${err instanceof Error ? err.message : String(err)}"`);
24338
+ console.error(`${TAG46} tick-failed error="${err instanceof Error ? err.message : String(err)}"`);
23974
24339
  } finally {
23975
24340
  try {
23976
24341
  await session.close();
@@ -23987,8 +24352,8 @@ function startTimeEntryCensus(openSession, opts = {}) {
23987
24352
  }
23988
24353
 
23989
24354
  // app/lib/ledger-census.ts
23990
- import { readdirSync as readdirSync25, readFileSync as readFileSync39, statSync as statSync15 } from "fs";
23991
- import { join as join39 } from "path";
24355
+ import { readdirSync as readdirSync25, readFileSync as readFileSync40, statSync as statSync15 } from "fs";
24356
+ import { join as join41 } from "path";
23992
24357
 
23993
24358
  // ../lib/ledger-core/dist/reconcile.js
23994
24359
  var LEDGER_INDEX_NAMES = [
@@ -24031,7 +24396,7 @@ function formatCensusLine(f) {
24031
24396
  }
24032
24397
 
24033
24398
  // app/lib/ledger-census.ts
24034
- var TAG46 = "[ledger-census]";
24399
+ var TAG47 = "[ledger-census]";
24035
24400
  var DAY_MS = 864e5;
24036
24401
  function countWriteRejects24h(logDir, now) {
24037
24402
  let entries;
@@ -24042,10 +24407,10 @@ function countWriteRejects24h(logDir, now) {
24042
24407
  }
24043
24408
  let total = 0;
24044
24409
  for (const name of entries) {
24045
- const path3 = join39(logDir, name);
24410
+ const path3 = join41(logDir, name);
24046
24411
  try {
24047
24412
  if (now - statSync15(path3).mtimeMs > DAY_MS) continue;
24048
- for (const line of readFileSync39(path3, "utf8").split("\n")) {
24413
+ for (const line of readFileSync40(path3, "utf8").split("\n")) {
24049
24414
  if (line.includes("[graph-write] reject")) total += 1;
24050
24415
  }
24051
24416
  } catch {
@@ -24107,7 +24472,7 @@ async function runLedgerCensusOnce(session, now) {
24107
24472
  else console.log(line);
24108
24473
  return finding;
24109
24474
  } catch (err) {
24110
- console.error(`${TAG46} error="${err instanceof Error ? err.message : String(err)}"`);
24475
+ console.error(`${TAG47} error="${err instanceof Error ? err.message : String(err)}"`);
24111
24476
  return null;
24112
24477
  }
24113
24478
  }
@@ -24120,7 +24485,7 @@ function startLedgerCensus(openSession, opts = {}) {
24120
24485
  session = openSession();
24121
24486
  } catch (err) {
24122
24487
  console.error(
24123
- `${TAG46} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`
24488
+ `${TAG47} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`
24124
24489
  );
24125
24490
  return;
24126
24491
  }
@@ -24128,7 +24493,7 @@ function startLedgerCensus(openSession, opts = {}) {
24128
24493
  await runLedgerCensusOnce(session, Date.now());
24129
24494
  } catch (err) {
24130
24495
  console.error(
24131
- `${TAG46} tick-failed error="${err instanceof Error ? err.message : String(err)}"`
24496
+ `${TAG47} tick-failed error="${err instanceof Error ? err.message : String(err)}"`
24132
24497
  );
24133
24498
  } finally {
24134
24499
  try {
@@ -24146,7 +24511,7 @@ function startLedgerCensus(openSession, opts = {}) {
24146
24511
  }
24147
24512
 
24148
24513
  // app/lib/conversation-audit.ts
24149
- var TAG47 = "[conversation-audit]";
24514
+ var TAG48 = "[conversation-audit]";
24150
24515
  async function runConversationAudit() {
24151
24516
  const session = getSession();
24152
24517
  try {
@@ -24165,19 +24530,30 @@ async function runConversationAudit() {
24165
24530
  size([p IN forensic WHERE p IS NOT NULL AND NOT p IN anchored]) AS unreconciled`,
24166
24531
  {}
24167
24532
  );
24533
+ const multiRes = await session.run(
24534
+ `MATCH (a:Conversation {channel: 'whatsapp'})
24535
+ WHERE a.channelAddress =~ '[0-9]+'
24536
+ WITH a.channelAddress AS addr, count(DISTINCT a.accountId) AS acctCount
24537
+ RETURN count(addr) AS senders,
24538
+ sum(CASE WHEN acctCount > 1 THEN 1 ELSE 0 END) AS multiAnchored`,
24539
+ {}
24540
+ );
24168
24541
  const rec = res.records[0];
24542
+ const multiRec = multiRes.records[0];
24169
24543
  const result = {
24170
24544
  total: Number(rec?.get("total") ?? 0),
24171
24545
  unreconciled: Number(rec?.get("unreconciled") ?? 0),
24172
- unpairable: Number(rec?.get("unpairable") ?? 0)
24546
+ unpairable: Number(rec?.get("unpairable") ?? 0),
24547
+ senders: Number(multiRec?.get("senders") ?? 0),
24548
+ multiAnchored: Number(multiRec?.get("multiAnchored") ?? 0)
24173
24549
  };
24174
24550
  console.error(
24175
- `${TAG47} channel=whatsapp total=${result.total} unreconciled=${result.unreconciled} unpairable=${result.unpairable}`
24551
+ `${TAG48} channel=whatsapp total=${result.total} senders=${result.senders} multiAnchored=${result.multiAnchored} unreconciled=${result.unreconciled} unpairable=${result.unpairable}`
24176
24552
  );
24177
24553
  return result;
24178
24554
  } catch (err) {
24179
24555
  const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
24180
- console.error(`${TAG47} op=failed reason=${reason}`);
24556
+ console.error(`${TAG48} op=failed reason=${reason}`);
24181
24557
  return null;
24182
24558
  } finally {
24183
24559
  await session.close();
@@ -24303,13 +24679,13 @@ function startGraphHealthTimer() {
24303
24679
 
24304
24680
  // app/lib/file-watcher.ts
24305
24681
  import * as fsp2 from "fs/promises";
24306
- import { resolve as resolve30, sep as sep8 } from "path";
24682
+ import { resolve as resolve31, sep as sep9 } from "path";
24307
24683
  var ACCOUNT_UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24308
24684
  var DEFAULT_COALESCE_MS = 500;
24309
24685
  var ROOTS = ["accounts"];
24310
24686
  function _routeEvent(rootName, filename) {
24311
24687
  if (!filename) return null;
24312
- const segs = filename.split(sep8).filter(Boolean);
24688
+ const segs = filename.split(sep9).filter(Boolean);
24313
24689
  if (segs.length < 2) return null;
24314
24690
  const accountId = segs[0];
24315
24691
  if (!ACCOUNT_UUID_RE4.test(accountId)) return null;
@@ -24322,7 +24698,7 @@ async function startFileWatcher(opts = {}) {
24322
24698
  const dropFn = opts.drop ?? dropFileIndex;
24323
24699
  for (const r of ROOTS) {
24324
24700
  try {
24325
- await fsp2.mkdir(resolve30(dataRoot, r), { recursive: true });
24701
+ await fsp2.mkdir(resolve31(dataRoot, r), { recursive: true });
24326
24702
  } catch (err) {
24327
24703
  console.error(
24328
24704
  `[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
@@ -24343,7 +24719,7 @@ async function startFileWatcher(opts = {}) {
24343
24719
  timers.set(relativePath, t);
24344
24720
  }
24345
24721
  async function runHook(relativePath, accountId) {
24346
- const absolute = resolve30(dataRoot, relativePath);
24722
+ const absolute = resolve31(dataRoot, relativePath);
24347
24723
  let exists = false;
24348
24724
  try {
24349
24725
  const st = await fsp2.stat(absolute);
@@ -24367,7 +24743,7 @@ async function startFileWatcher(opts = {}) {
24367
24743
  }
24368
24744
  }
24369
24745
  async function watchRoot(rootName) {
24370
- const absRoot = resolve30(dataRoot, rootName);
24746
+ const absRoot = resolve31(dataRoot, rootName);
24371
24747
  try {
24372
24748
  const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
24373
24749
  for await (const event of iter) {
@@ -24406,7 +24782,7 @@ async function startFileWatcher(opts = {}) {
24406
24782
 
24407
24783
  // app/lib/migrate-uploads.ts
24408
24784
  import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
24409
- import { dirname as dirname13, relative as relative6, resolve as resolve31 } from "path";
24785
+ import { dirname as dirname13, relative as relative6, resolve as resolve32 } from "path";
24410
24786
  var ACCOUNT_UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24411
24787
  async function walkFiles(dir) {
24412
24788
  const out = [];
@@ -24417,7 +24793,7 @@ async function walkFiles(dir) {
24417
24793
  return out;
24418
24794
  }
24419
24795
  for (const e of entries) {
24420
- const abs = resolve31(dir, e.name);
24796
+ const abs = resolve32(dir, e.name);
24421
24797
  if (e.isDirectory()) {
24422
24798
  out.push(...await walkFiles(abs));
24423
24799
  } else if (e.isFile()) {
@@ -24437,7 +24813,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
24437
24813
  let moved = 0;
24438
24814
  for (const oldAbs of files) {
24439
24815
  const suffix = relative6(srcDir, oldAbs);
24440
- const newAbs = resolve31(destDir, suffix);
24816
+ const newAbs = resolve32(destDir, suffix);
24441
24817
  await mkdir5(dirname13(newAbs), { recursive: true });
24442
24818
  await rename2(oldAbs, newAbs);
24443
24819
  moved++;
@@ -24464,7 +24840,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
24464
24840
  }
24465
24841
  async function migrateUploads(opts = {}) {
24466
24842
  const dataRoot = opts.dataRoot ?? DATA_ROOT;
24467
- const oldRoot = resolve31(dataRoot, "uploads");
24843
+ const oldRoot = resolve32(dataRoot, "uploads");
24468
24844
  let topEntries;
24469
24845
  try {
24470
24846
  topEntries = await readdir5(oldRoot, { withFileTypes: true });
@@ -24485,8 +24861,8 @@ async function migrateUploads(opts = {}) {
24485
24861
  const name = entry.name;
24486
24862
  if (ACCOUNT_UUID_RE5.test(name)) {
24487
24863
  moved += await relocateTree(
24488
- resolve31(oldRoot, name),
24489
- resolve31(dataRoot, "accounts", name, "uploads"),
24864
+ resolve32(oldRoot, name),
24865
+ resolve32(dataRoot, "accounts", name, "uploads"),
24490
24866
  dataRoot,
24491
24867
  name,
24492
24868
  session
@@ -24498,8 +24874,8 @@ async function migrateUploads(opts = {}) {
24498
24874
  continue;
24499
24875
  }
24500
24876
  moved += await relocateTree(
24501
- resolve31(oldRoot, "public"),
24502
- resolve31(dataRoot, "accounts", installAccountId, "uploads", "public"),
24877
+ resolve32(oldRoot, "public"),
24878
+ resolve32(dataRoot, "accounts", installAccountId, "uploads", "public"),
24503
24879
  dataRoot,
24504
24880
  null,
24505
24881
  // public uploads carry no graph nodes
@@ -24528,9 +24904,9 @@ async function migrateUploads(opts = {}) {
24528
24904
  }
24529
24905
 
24530
24906
  // app/lib/migrate-admin-webchat-sidecars.ts
24531
- import { readdir as readdir6, readFile as readFile6, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
24907
+ import { readdir as readdir6, readFile as readFile7, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
24532
24908
  import { existsSync as existsSync36 } from "fs";
24533
- import { join as join40 } from "path";
24909
+ import { join as join42 } from "path";
24534
24910
  var ADMIN_ROLE2 = "admin";
24535
24911
  var WEBCHAT_CHANNEL2 = "webchat";
24536
24912
  var SESSION_META_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
@@ -24538,10 +24914,10 @@ var defaultResolveName = async (_accountId, userId) => {
24538
24914
  const res = await loadAdminUserName(userId);
24539
24915
  return res.source === "neo4j" && res.joined.length > 0 ? res.joined : null;
24540
24916
  };
24541
- async function readRaw2(path3) {
24917
+ async function readRaw3(path3) {
24542
24918
  let raw;
24543
24919
  try {
24544
- raw = await readFile6(path3, "utf8");
24920
+ raw = await readFile7(path3, "utf8");
24545
24921
  } catch {
24546
24922
  return null;
24547
24923
  }
@@ -24575,7 +24951,7 @@ async function collectLiveSidecars(projectsRoot) {
24575
24951
  }
24576
24952
  for (const slug of slugs) {
24577
24953
  if (!slug.isDirectory()) continue;
24578
- const slugDir = join40(projectsRoot, slug.name);
24954
+ const slugDir = join42(projectsRoot, slug.name);
24579
24955
  let entries;
24580
24956
  try {
24581
24957
  entries = await readdir6(slugDir, { withFileTypes: true });
@@ -24584,9 +24960,9 @@ async function collectLiveSidecars(projectsRoot) {
24584
24960
  }
24585
24961
  for (const entry of entries) {
24586
24962
  if (entry.isFile() && SESSION_META_RE.test(entry.name)) {
24587
- out.push(join40(slugDir, entry.name));
24963
+ out.push(join42(slugDir, entry.name));
24588
24964
  } else if (entry.isDirectory() && entry.name === "subagents") {
24589
- const subDir = join40(slugDir, entry.name);
24965
+ const subDir = join42(slugDir, entry.name);
24590
24966
  let subs;
24591
24967
  try {
24592
24968
  subs = await readdir6(subDir, { withFileTypes: true });
@@ -24594,7 +24970,7 @@ async function collectLiveSidecars(projectsRoot) {
24594
24970
  continue;
24595
24971
  }
24596
24972
  for (const s of subs) {
24597
- if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join40(subDir, s.name));
24973
+ if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join42(subDir, s.name));
24598
24974
  }
24599
24975
  }
24600
24976
  }
@@ -24606,7 +24982,7 @@ function shortId(path3) {
24606
24982
  return base.slice(0, 8);
24607
24983
  }
24608
24984
  async function migrateAdminWebchatSidecars(opts = {}) {
24609
- const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join40(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
24985
+ const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join42(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
24610
24986
  const usersFile = opts.usersFile ?? USERS_FILE;
24611
24987
  const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
24612
24988
  const resolveName = opts.resolveName ?? defaultResolveName;
@@ -24646,7 +25022,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
24646
25022
  };
24647
25023
  const sidecars = await collectLiveSidecars(projectsRoot);
24648
25024
  for (const path3 of sidecars) {
24649
- const raw = await readRaw2(path3);
25025
+ const raw = await readRaw3(path3);
24650
25026
  if (!raw) continue;
24651
25027
  if (raw.role !== ADMIN_ROLE2 || raw.channel !== WEBCHAT_CHANNEL2) continue;
24652
25028
  result.scanned++;
@@ -24664,7 +25040,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
24664
25040
  await writeRaw(path3, raw);
24665
25041
  result.backfilled++;
24666
25042
  await resolveAdminName();
24667
- const back = await readRaw2(path3);
25043
+ const back = await readRaw3(path3);
24668
25044
  const wrote = back && back.adminUserId === soleAdminUserId;
24669
25045
  if (wrote && resolvedNameForAdmin) {
24670
25046
  result.resolvedName++;
@@ -24951,8 +25327,8 @@ var streamSSE = (c, cb, onError) => {
24951
25327
 
24952
25328
  // app/lib/whatsapp/gateway/routes.ts
24953
25329
  function createWaChannelRoutes(deps) {
24954
- const app64 = new Hono();
24955
- app64.get("/wa-channel/inbound", (c) => {
25330
+ const app65 = new Hono();
25331
+ app65.get("/wa-channel/inbound", (c) => {
24956
25332
  const senderId = c.req.query("senderId");
24957
25333
  if (!senderId) return c.json({ error: "senderId required" }, 400);
24958
25334
  return streamSSE(c, async (stream2) => {
@@ -24960,9 +25336,11 @@ function createWaChannelRoutes(deps) {
24960
25336
  void stream2.writeSSE({ data: JSON.stringify(payload) });
24961
25337
  });
24962
25338
  console.error(`[whatsapp-native] op=channel-attached senderId=${senderId}`);
25339
+ deps.onAttached?.(senderId);
24963
25340
  stream2.onAbort(() => {
24964
25341
  deps.hub.detach(senderId);
24965
25342
  console.error(`[whatsapp-native] op=channel-detached senderId=${senderId}`);
25343
+ deps.onDetached?.(senderId);
24966
25344
  });
24967
25345
  while (!stream2.aborted) {
24968
25346
  await stream2.sleep(15e3);
@@ -24970,7 +25348,7 @@ function createWaChannelRoutes(deps) {
24970
25348
  }
24971
25349
  });
24972
25350
  });
24973
- app64.post("/wa-channel/reply", async (c) => {
25351
+ app65.post("/wa-channel/reply", async (c) => {
24974
25352
  const body = await c.req.json().catch(() => null);
24975
25353
  const senderId = body?.senderId;
24976
25354
  const text = body?.text;
@@ -24991,7 +25369,7 @@ function createWaChannelRoutes(deps) {
24991
25369
  console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
24992
25370
  return c.json({ ok: true });
24993
25371
  });
24994
- app64.post("/wa-channel/reply-document", async (c) => {
25372
+ app65.post("/wa-channel/reply-document", async (c) => {
24995
25373
  const body = await c.req.json().catch(() => null);
24996
25374
  const senderId = body?.senderId;
24997
25375
  const files = body?.files;
@@ -25030,7 +25408,7 @@ function createWaChannelRoutes(deps) {
25030
25408
  }
25031
25409
  return c.json({ ok: true, results });
25032
25410
  });
25033
- app64.post("/wa-channel/ready", async (c) => {
25411
+ app65.post("/wa-channel/ready", async (c) => {
25034
25412
  const body = await c.req.json().catch(() => null);
25035
25413
  const senderId = body?.senderId;
25036
25414
  if (typeof senderId !== "string") {
@@ -25039,7 +25417,7 @@ function createWaChannelRoutes(deps) {
25039
25417
  deps.onReady?.(senderId);
25040
25418
  return c.json({ ok: true });
25041
25419
  });
25042
- app64.post("/wa-channel/received", async (c) => {
25420
+ app65.post("/wa-channel/received", async (c) => {
25043
25421
  const body = await c.req.json().catch(() => null);
25044
25422
  const senderId = body?.senderId;
25045
25423
  const waMessageId = body?.waMessageId;
@@ -25049,7 +25427,7 @@ function createWaChannelRoutes(deps) {
25049
25427
  deps.onReceived?.(senderId, waMessageId);
25050
25428
  return c.json({ ok: true });
25051
25429
  });
25052
- app64.post("/wa-channel/turn-end", async (c) => {
25430
+ app65.post("/wa-channel/turn-end", async (c) => {
25053
25431
  const body = await c.req.json().catch(() => null);
25054
25432
  const senderId = body?.senderId;
25055
25433
  const sessionId = body?.sessionId;
@@ -25082,7 +25460,7 @@ function createWaChannelRoutes(deps) {
25082
25460
  console.error(`[whatsapp-native] op=turn-end sessionId=${sid} replied=no delivered=none`);
25083
25461
  return c.json({ ok: true, delivered: "none" });
25084
25462
  });
25085
- return app64;
25463
+ return app65;
25086
25464
  }
25087
25465
 
25088
25466
  // app/lib/whatsapp/gateway/wa-gateway.ts
@@ -25104,9 +25482,30 @@ var WaGateway = class {
25104
25482
  spawning = /* @__PURE__ */ new Set();
25105
25483
  seq = 0;
25106
25484
  /** Hono sub-app exposing the loopback channel routes; mount on the UI app. */
25485
+ /** Task 1789 — senderId -> the ms at which its channel server last detached,
25486
+ * cleared on attach. The standing audit reads this; nothing else does. */
25487
+ detachedAt = /* @__PURE__ */ new Map();
25488
+ /** Seat lost. `atMs` is injected so the audit's accounting is testable. */
25489
+ recordDetached(senderId, atMs) {
25490
+ this.detachedAt.set(senderId, atMs);
25491
+ }
25492
+ /** Seat regained — the sender is no longer unseated. */
25493
+ recordAttached(senderId) {
25494
+ this.detachedAt.delete(senderId);
25495
+ }
25496
+ /** Senders with no channel server for longer than `olderThanMs`. */
25497
+ unseatedSenders(nowMs, olderThanMs) {
25498
+ const out = [];
25499
+ for (const [senderId, sinceMs] of this.detachedAt) {
25500
+ if (nowMs - sinceMs > olderThanMs) out.push({ senderId, sinceMs });
25501
+ }
25502
+ return out.sort((a, b) => a.senderId.localeCompare(b.senderId));
25503
+ }
25107
25504
  routes() {
25108
25505
  return createWaChannelRoutes({
25109
25506
  hub: this.hub,
25507
+ onAttached: (senderId) => this.recordAttached(senderId),
25508
+ onDetached: (senderId) => this.recordDetached(senderId, Date.now()),
25110
25509
  sendOutbound: (senderId, text) => this.sendOutbound(senderId, text),
25111
25510
  sendDocument: (senderId, filePath, caption) => this.sendDocument(senderId, filePath, caption),
25112
25511
  onReady: (senderId) => {
@@ -25217,7 +25616,7 @@ var WaGateway = class {
25217
25616
  },
25218
25617
  Date.now()
25219
25618
  );
25220
- const resolvedSessionId = adminSessionIdFor(
25619
+ const resolvedSessionId = (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.effectiveAccountId, senderId) : null) || adminSessionIdFor(
25221
25620
  input.effectiveAccountId,
25222
25621
  senderId,
25223
25622
  input.role === "public" && input.personId ? input.personId : void 0
@@ -25285,17 +25684,28 @@ function logDuplicateSenderGroups(groups) {
25285
25684
  );
25286
25685
  }
25287
25686
  }
25687
+ function logUnseatedSenders(entries, nowMs) {
25688
+ for (const e of entries) {
25689
+ const secs = Math.round((nowMs - e.sinceMs) / 1e3);
25690
+ console.error(`[whatsapp-native] op=sender-unseated senderId=${e.senderId} since=${new Date(e.sinceMs).toISOString()} forSeconds=${secs}`);
25691
+ }
25692
+ }
25288
25693
 
25289
25694
  // app/lib/whatsapp/gateway/spawn-request.ts
25290
25695
  function buildWaSpawnRequest(input) {
25291
25696
  const personId = input.role === "public" && input.personId ? input.personId : void 0;
25292
25697
  return {
25293
- // Task 1746 — the deterministic per-sender id is the ONLY id a sender's
25294
- // session ever has. Task 1526's per-firing seat override used to replace it
25295
- // on a scheduled cold start, which spawned a second session for the sender;
25296
- // that session then attached under the sender's own hub key and swallowed
25297
- // their real messages until it detached, splitting the conversation in two.
25298
- sessionId: adminSessionIdFor(input.accountId, input.senderId, personId),
25698
+ // Task 1746 — the deterministic per-sender id is the DEFAULT id a sender's
25699
+ // session has. Task 1526's per-firing seat override used to replace it on a
25700
+ // scheduled cold start, which spawned a second session per firing; that
25701
+ // session then attached under the sender's own hub key and swallowed their
25702
+ // real messages until it detached, splitting the conversation in two.
25703
+ //
25704
+ // Task 1789's override is not that. It is a stable per-sender forward
25705
+ // pointer written once by an operator re-seat and resolving identically for
25706
+ // every subsequent inbound, so it can never yield a second id for a sender.
25707
+ // Do not remove it without reading server/channel-session-override.ts.
25708
+ sessionId: input.overrideSessionId || adminSessionIdFor(input.accountId, input.senderId, personId),
25299
25709
  role: input.role,
25300
25710
  channel: "whatsapp",
25301
25711
  personId,
@@ -25479,8 +25889,8 @@ var import_dist7 = __toESM(require_dist(), 1);
25479
25889
 
25480
25890
  // app/lib/webchat/gateway/routes.ts
25481
25891
  function createWebchatChannelRoutes(deps) {
25482
- const app64 = new Hono();
25483
- app64.get("/webchat-channel/inbound", (c) => {
25892
+ const app65 = new Hono();
25893
+ app65.get("/webchat-channel/inbound", (c) => {
25484
25894
  const key = c.req.query("key");
25485
25895
  if (!key) return c.json({ error: "key required" }, 400);
25486
25896
  return streamSSE(c, async (stream2) => {
@@ -25498,7 +25908,7 @@ function createWebchatChannelRoutes(deps) {
25498
25908
  }
25499
25909
  });
25500
25910
  });
25501
- app64.post("/webchat-channel/reply", async (c) => {
25911
+ app65.post("/webchat-channel/reply", async (c) => {
25502
25912
  const body = await c.req.json().catch(() => null);
25503
25913
  const key = body?.key;
25504
25914
  const text = body?.text;
@@ -25508,7 +25918,7 @@ function createWebchatChannelRoutes(deps) {
25508
25918
  deps.onReply?.(key, text);
25509
25919
  return c.json({ ok: true });
25510
25920
  });
25511
- app64.post("/webchat-channel/connector-auth", async (c) => {
25921
+ app65.post("/webchat-channel/connector-auth", async (c) => {
25512
25922
  const body = await c.req.json().catch(() => null);
25513
25923
  const key = body?.key;
25514
25924
  const sessionId = body?.sessionId;
@@ -25521,7 +25931,7 @@ function createWebchatChannelRoutes(deps) {
25521
25931
  const result = await deps.onConnectorAuth({ key, sessionId, name, completed });
25522
25932
  return c.json(result);
25523
25933
  });
25524
- app64.post("/webchat-channel/ready", async (c) => {
25934
+ app65.post("/webchat-channel/ready", async (c) => {
25525
25935
  const body = await c.req.json().catch(() => null);
25526
25936
  const key = body?.key;
25527
25937
  if (typeof key !== "string") {
@@ -25530,7 +25940,7 @@ function createWebchatChannelRoutes(deps) {
25530
25940
  deps.onReady?.(key);
25531
25941
  return c.json({ ok: true });
25532
25942
  });
25533
- app64.post("/webchat-channel/permission-request", async (c) => {
25943
+ app65.post("/webchat-channel/permission-request", async (c) => {
25534
25944
  const body = await c.req.json().catch(() => null);
25535
25945
  const key = body?.key;
25536
25946
  const requestId = body?.request_id;
@@ -25544,7 +25954,7 @@ function createWebchatChannelRoutes(deps) {
25544
25954
  const verdict = await deps.awaitPermissionVerdict({ key, requestId, toolName, description, inputPreview });
25545
25955
  return c.json(verdict);
25546
25956
  });
25547
- app64.post("/webchat-channel/received", async (c) => {
25957
+ app65.post("/webchat-channel/received", async (c) => {
25548
25958
  const body = await c.req.json().catch(() => null);
25549
25959
  const key = body?.key;
25550
25960
  const messageId = body?.messageId;
@@ -25554,7 +25964,7 @@ function createWebchatChannelRoutes(deps) {
25554
25964
  deps.onReceived?.(key, messageId);
25555
25965
  return c.json({ ok: true });
25556
25966
  });
25557
- app64.post("/webchat-channel/turn-end", async (c) => {
25967
+ app65.post("/webchat-channel/turn-end", async (c) => {
25558
25968
  const body = await c.req.json().catch(() => null);
25559
25969
  const key = body?.key;
25560
25970
  const sessionId = body?.sessionId;
@@ -25577,7 +25987,7 @@ function createWebchatChannelRoutes(deps) {
25577
25987
  console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} replied=no delivered=none`);
25578
25988
  return c.json({ ok: true, delivered: "none" });
25579
25989
  });
25580
- return app64;
25990
+ return app65;
25581
25991
  }
25582
25992
 
25583
25993
  // app/lib/webchat/gateway/webchat-gateway.ts
@@ -25635,16 +26045,16 @@ var WebchatGateway = class _WebchatGateway {
25635
26045
  * The public /api/chat route awaits this to return the reply on the same SSE
25636
26046
  * response, preserving the pre-756 POST→single-blob browser contract. */
25637
26047
  awaitReply(key, timeoutMs) {
25638
- return new Promise((resolve37) => {
26048
+ return new Promise((resolve38) => {
25639
26049
  const timer = setTimeout(() => {
25640
26050
  this.replyAwaiters.delete(key);
25641
- resolve37({ timeout: true });
26051
+ resolve38({ timeout: true });
25642
26052
  }, timeoutMs);
25643
26053
  if (timer.unref) timer.unref();
25644
26054
  this.replyAwaiters.set(key, (r) => {
25645
26055
  clearTimeout(timer);
25646
26056
  this.replyAwaiters.delete(key);
25647
- resolve37(r);
26057
+ resolve38(r);
25648
26058
  });
25649
26059
  });
25650
26060
  }
@@ -25655,11 +26065,11 @@ var WebchatGateway = class _WebchatGateway {
25655
26065
  * terminal dialog stays answerable). */
25656
26066
  awaitPermissionVerdict(p, timeoutMs) {
25657
26067
  const k = _WebchatGateway.promptKey(p.key, p.requestId);
25658
- return new Promise((resolve37) => {
26068
+ return new Promise((resolve38) => {
25659
26069
  this.pendingPrompts.get(k)?.resolve({ timeout: true });
25660
26070
  const timer = setTimeout(() => {
25661
26071
  this.pendingPrompts.delete(k);
25662
- resolve37({ timeout: true });
26072
+ resolve38({ timeout: true });
25663
26073
  }, timeoutMs);
25664
26074
  if (timer.unref) timer.unref();
25665
26075
  this.pendingPrompts.set(k, {
@@ -25668,7 +26078,7 @@ var WebchatGateway = class _WebchatGateway {
25668
26078
  resolve: (r) => {
25669
26079
  clearTimeout(timer);
25670
26080
  this.pendingPrompts.delete(k);
25671
- resolve37(r);
26081
+ resolve38(r);
25672
26082
  }
25673
26083
  });
25674
26084
  console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
@@ -26233,7 +26643,7 @@ async function fanOut(subscribers, text, onError, tag) {
26233
26643
 
26234
26644
  // app/lib/webchat/file-delivery.ts
26235
26645
  import { realpathSync as realpathSync8 } from "fs";
26236
- import { resolve as resolve32 } from "path";
26646
+ import { resolve as resolve33 } from "path";
26237
26647
 
26238
26648
  // app/lib/channel-pty-bridge/file-delivery.ts
26239
26649
  var SEND_USER_FILE = "SendUserFile";
@@ -26324,29 +26734,29 @@ function makeFileDelivery(opts) {
26324
26734
  }
26325
26735
 
26326
26736
  // app/lib/webchat/file-delivery.ts
26327
- var TAG48 = "[webchat-adaptor]";
26737
+ var TAG49 = "[webchat-adaptor]";
26328
26738
  function platformRoot2() {
26329
26739
  return process.env.MAXY_PLATFORM_ROOT || "";
26330
26740
  }
26331
26741
  function makeWebchatSendFile(entry) {
26332
26742
  return async (filePath) => {
26333
26743
  if (!entry.accountId) {
26334
- console.error(`${TAG48} file-delivery reject reason=no-account sender=${entry.senderId}`);
26744
+ console.error(`${TAG49} file-delivery reject reason=no-account sender=${entry.senderId}`);
26335
26745
  return { ok: false, error: "no-account" };
26336
26746
  }
26337
- const accountDir = resolve32(platformRoot2(), "..", "data/accounts", entry.accountId);
26747
+ const accountDir = resolve33(platformRoot2(), "..", "data/accounts", entry.accountId);
26338
26748
  try {
26339
26749
  const resolved = realpathSync8(filePath);
26340
26750
  const accountResolved = realpathSync8(accountDir);
26341
26751
  if (!resolved.startsWith(accountResolved + "/")) {
26342
- console.error(`${TAG48} file-delivery reject reason=outside_account_directory sender=${entry.senderId}`);
26752
+ console.error(`${TAG49} file-delivery reject reason=outside_account_directory sender=${entry.senderId}`);
26343
26753
  return { ok: false, error: "outside-account" };
26344
26754
  }
26345
26755
  return { ok: true };
26346
26756
  } catch (err) {
26347
26757
  const code = err.code;
26348
26758
  console.error(
26349
- `${TAG48} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry.senderId}`
26759
+ `${TAG49} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry.senderId}`
26350
26760
  );
26351
26761
  return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
26352
26762
  }
@@ -26355,7 +26765,7 @@ function makeWebchatSendFile(entry) {
26355
26765
  function makeWebchatFileDelivery(entry) {
26356
26766
  return makeFileDelivery({
26357
26767
  entry,
26358
- tag: TAG48,
26768
+ tag: TAG49,
26359
26769
  channel: "webchat",
26360
26770
  sendFile: makeWebchatSendFile(entry),
26361
26771
  deferUntilVerdict: true
@@ -26417,7 +26827,7 @@ function buildPublicWebchatSpawnRequest(input) {
26417
26827
  }
26418
26828
 
26419
26829
  // app/lib/whatsapp/inbound/file-delivery-bridge.ts
26420
- var TAG49 = "[whatsapp-adaptor]";
26830
+ var TAG50 = "[whatsapp-adaptor]";
26421
26831
  var SEND_USER_FILE2 = "SendUserFile";
26422
26832
  var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
26423
26833
  function platformRoot3() {
@@ -26435,7 +26845,7 @@ function makeWhatsAppSendFile(entry, maxyAccountId) {
26435
26845
  });
26436
26846
  if (result.ok) return { ok: true };
26437
26847
  console.error(
26438
- `${TAG49} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
26848
+ `${TAG50} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
26439
26849
  );
26440
26850
  return { ok: false, error: result.error };
26441
26851
  };
@@ -26443,7 +26853,7 @@ function makeWhatsAppSendFile(entry, maxyAccountId) {
26443
26853
  function makeWhatsAppFileDelivery(entry, maxyAccountId) {
26444
26854
  const shared = makeFileDelivery({
26445
26855
  entry,
26446
- tag: TAG49,
26856
+ tag: TAG50,
26447
26857
  channel: "whatsapp",
26448
26858
  sendFile: makeWhatsAppSendFile(entry, maxyAccountId)
26449
26859
  });
@@ -26478,7 +26888,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
26478
26888
  if (!delivered) {
26479
26889
  const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
26480
26890
  console.error(
26481
- `${TAG49} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
26891
+ `${TAG50} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
26482
26892
  );
26483
26893
  }
26484
26894
  }
@@ -26664,8 +27074,8 @@ var import_dist8 = __toESM(require_dist(), 1);
26664
27074
 
26665
27075
  // app/lib/telegram/gateway/routes.ts
26666
27076
  function createTelegramChannelRoutes(deps) {
26667
- const app64 = new Hono();
26668
- app64.get("/tg-channel/inbound", (c) => {
27077
+ const app65 = new Hono();
27078
+ app65.get("/tg-channel/inbound", (c) => {
26669
27079
  const key = c.req.query("key");
26670
27080
  if (!key) return c.json({ error: "key required" }, 400);
26671
27081
  return streamSSE(c, async (stream2) => {
@@ -26683,7 +27093,7 @@ function createTelegramChannelRoutes(deps) {
26683
27093
  }
26684
27094
  });
26685
27095
  });
26686
- app64.post("/tg-channel/reply", async (c) => {
27096
+ app65.post("/tg-channel/reply", async (c) => {
26687
27097
  const body = await c.req.json().catch(() => null);
26688
27098
  const key = body?.key;
26689
27099
  const text = body?.text;
@@ -26699,7 +27109,7 @@ function createTelegramChannelRoutes(deps) {
26699
27109
  console.error(`[telegram-native] op=reply key=${key} bytes=${Buffer.byteLength(text, "utf8")}`);
26700
27110
  return c.json({ ok: true });
26701
27111
  });
26702
- app64.post("/tg-channel/ready", async (c) => {
27112
+ app65.post("/tg-channel/ready", async (c) => {
26703
27113
  const body = await c.req.json().catch(() => null);
26704
27114
  const key = body?.key;
26705
27115
  if (typeof key !== "string") {
@@ -26708,7 +27118,7 @@ function createTelegramChannelRoutes(deps) {
26708
27118
  deps.onReady?.(key);
26709
27119
  return c.json({ ok: true });
26710
27120
  });
26711
- app64.post("/tg-channel/received", async (c) => {
27121
+ app65.post("/tg-channel/received", async (c) => {
26712
27122
  const body = await c.req.json().catch(() => null);
26713
27123
  const key = body?.key;
26714
27124
  const messageId = body?.messageId;
@@ -26718,7 +27128,7 @@ function createTelegramChannelRoutes(deps) {
26718
27128
  deps.onReceived?.(key, messageId);
26719
27129
  return c.json({ ok: true });
26720
27130
  });
26721
- app64.post("/tg-channel/turn-end", async (c) => {
27131
+ app65.post("/tg-channel/turn-end", async (c) => {
26722
27132
  const body = await c.req.json().catch(() => null);
26723
27133
  const key = body?.key;
26724
27134
  const sessionId = body?.sessionId;
@@ -26751,7 +27161,7 @@ function createTelegramChannelRoutes(deps) {
26751
27161
  console.error(`[telegram-native] op=turn-end key=${key} sessionId=${sid} replied=no delivered=none`);
26752
27162
  return c.json({ ok: true, delivered: "none" });
26753
27163
  });
26754
- return app64;
27164
+ return app65;
26755
27165
  }
26756
27166
 
26757
27167
  // app/lib/telegram/gateway/telegram-gateway.ts
@@ -26842,7 +27252,7 @@ var TelegramGateway = class {
26842
27252
  { key: input.senderId, text: input.text, messageId: `tg-${++this.seq}`, source, standingRules, scheduleProvenance: input.scheduleProvenance },
26843
27253
  Date.now()
26844
27254
  );
26845
- const resolvedSessionId = adminSessionIdFor(
27255
+ const resolvedSessionId = (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.accountId, input.senderId) : null) || adminSessionIdFor(
26846
27256
  input.accountId,
26847
27257
  input.senderId,
26848
27258
  input.role === "public" && input.personId ? input.personId : void 0
@@ -26879,10 +27289,11 @@ var TelegramGateway = class {
26879
27289
  function buildTelegramSpawnRequest(input) {
26880
27290
  const personId = input.role === "public" && input.personId ? input.personId : void 0;
26881
27291
  return {
26882
- // Task 1746 — the deterministic per-sender id is the ONLY id a sender's
26883
- // session ever has. Mirror of the WhatsApp builder; see its comment for why
26884
- // Task 1526's per-firing override was removed.
26885
- sessionId: adminSessionIdFor(input.accountId, input.senderId, personId),
27292
+ // Task 1746 / 1789 — the deterministic per-sender id is the DEFAULT; an
27293
+ // operator re-seat sets a stable per-sender forward pointer that wins here.
27294
+ // Mirror of the WhatsApp builder; see its comment for the full distinction
27295
+ // between this pointer and Task 1526's removed per-firing seat.
27296
+ sessionId: input.overrideSessionId || adminSessionIdFor(input.accountId, input.senderId, personId),
26886
27297
  role: input.role,
26887
27298
  channel: "telegram",
26888
27299
  personId,
@@ -26899,9 +27310,9 @@ function buildTelegramSpawnRequest(input) {
26899
27310
 
26900
27311
  // app/lib/telegram/outbound/send-document.ts
26901
27312
  import { realpathSync as realpathSync9 } from "fs";
26902
- import { readFile as readFile7, stat as stat7 } from "fs/promises";
26903
- import { resolve as resolve33, basename as basename11 } from "path";
26904
- var TAG50 = "[telegram:outbound]";
27313
+ import { readFile as readFile8, stat as stat8 } from "fs/promises";
27314
+ import { resolve as resolve34, basename as basename11 } from "path";
27315
+ var TAG51 = "[telegram:outbound]";
26905
27316
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
26906
27317
  async function sendTelegramDocument(input) {
26907
27318
  const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot5 } = input;
@@ -26911,25 +27322,25 @@ async function sendTelegramDocument(input) {
26911
27322
  if (!maxyAccountId || !platformRoot5) {
26912
27323
  return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
26913
27324
  }
26914
- const accountDir = resolve33(platformRoot5, "..", "data/accounts", maxyAccountId);
27325
+ const accountDir = resolve34(platformRoot5, "..", "data/accounts", maxyAccountId);
26915
27326
  let resolvedPath;
26916
27327
  try {
26917
27328
  resolvedPath = realpathSync9(filePath);
26918
27329
  const accountResolved = realpathSync9(accountDir);
26919
27330
  if (!resolvedPath.startsWith(accountResolved + "/")) {
26920
- console.error(`${TAG50} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
27331
+ console.error(`${TAG51} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
26921
27332
  return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
26922
27333
  }
26923
27334
  } catch (err) {
26924
27335
  const code = err.code;
26925
27336
  if (code === "ENOENT") {
26926
- console.error(`${TAG50} document ENOENT path=${filePath}`);
27337
+ console.error(`${TAG51} document ENOENT path=${filePath}`);
26927
27338
  return { ok: false, status: 404, error: `File not found: ${filePath}` };
26928
27339
  }
26929
- console.error(`${TAG50} document path error: ${String(err)}`);
27340
+ console.error(`${TAG51} document path error: ${String(err)}`);
26930
27341
  return { ok: false, status: 500, error: String(err) };
26931
27342
  }
26932
- const fileStat = await stat7(resolvedPath);
27343
+ const fileStat = await stat8(resolvedPath);
26933
27344
  if (fileStat.size > TELEGRAM_DOCUMENT_MAX_BYTES) {
26934
27345
  return {
26935
27346
  ok: false,
@@ -26938,7 +27349,7 @@ async function sendTelegramDocument(input) {
26938
27349
  };
26939
27350
  }
26940
27351
  const filename = basename11(resolvedPath);
26941
- const buffer = Buffer.from(await readFile7(resolvedPath));
27352
+ const buffer = Buffer.from(await readFile8(resolvedPath));
26942
27353
  const form = new FormData();
26943
27354
  form.append("chat_id", String(chatId));
26944
27355
  if (caption) form.append("caption", caption);
@@ -26959,13 +27370,13 @@ async function sendTelegramDocument(input) {
26959
27370
  error = e instanceof Error ? e.message : String(e);
26960
27371
  }
26961
27372
  console.error(
26962
- `${TAG50} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
27373
+ `${TAG51} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
26963
27374
  );
26964
27375
  return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
26965
27376
  }
26966
27377
 
26967
27378
  // app/lib/telegram/outbound/file-delivery.ts
26968
- var TAG51 = "[telegram:outbound]";
27379
+ var TAG52 = "[telegram:outbound]";
26969
27380
  function platformRoot4() {
26970
27381
  return process.env.MAXY_PLATFORM_ROOT || "";
26971
27382
  }
@@ -26977,11 +27388,11 @@ function makeTelegramSendFile(entry) {
26977
27388
  botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
26978
27389
  }
26979
27390
  if (!botToken) {
26980
- console.error(`${TAG51} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
27391
+ console.error(`${TAG52} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
26981
27392
  return { ok: false, error: "no-bot-token" };
26982
27393
  }
26983
27394
  if (entry.replyTarget == null) {
26984
- console.error(`${TAG51} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
27395
+ console.error(`${TAG52} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
26985
27396
  return { ok: false, error: "no-reply-target" };
26986
27397
  }
26987
27398
  const result = await sendTelegramDocument({
@@ -26998,7 +27409,7 @@ function makeTelegramSendFile(entry) {
26998
27409
  function makeTelegramFileDelivery(entry) {
26999
27410
  return makeFileDelivery({
27000
27411
  entry,
27001
- tag: TAG51,
27412
+ tag: TAG52,
27002
27413
  channel: "telegram",
27003
27414
  sendFile: makeTelegramSendFile(entry)
27004
27415
  });
@@ -27038,17 +27449,17 @@ function startTelegramNativeFileFollower(input) {
27038
27449
  }
27039
27450
 
27040
27451
  // server/telegram-descriptor.ts
27041
- import { resolve as resolve34, join as join41 } from "path";
27452
+ import { resolve as resolve35, join as join43 } from "path";
27042
27453
  function telegramGatewayUrl() {
27043
27454
  return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
27044
27455
  }
27045
27456
  function telegramServerPath() {
27046
- return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve34(process.env.MAXY_PLATFORM_ROOT ?? join41(__dirname, ".."), "services/telegram-channel/dist/server.js");
27457
+ return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve35(process.env.MAXY_PLATFORM_ROOT ?? join43(__dirname, ".."), "services/telegram-channel/dist/server.js");
27047
27458
  }
27048
27459
 
27049
27460
  // app/lib/channel-pty-bridge/public-session-end-review.ts
27050
27461
  import { randomUUID as randomUUID16 } from "crypto";
27051
- var TAG52 = "[public-session-review]";
27462
+ var TAG53 = "[public-session-review]";
27052
27463
  var CONSUMED_CAP = 500;
27053
27464
  var consumed = /* @__PURE__ */ new Set();
27054
27465
  function consumeOnce(sessionId) {
@@ -27075,7 +27486,7 @@ async function fetchJsonl(sessionId) {
27075
27486
  return await res.text();
27076
27487
  } catch (err) {
27077
27488
  console.error(
27078
- `${TAG52} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
27489
+ `${TAG53} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
27079
27490
  );
27080
27491
  return null;
27081
27492
  }
@@ -27099,7 +27510,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
27099
27510
  const res = await fetch(url);
27100
27511
  if (!res.ok) {
27101
27512
  console.error(
27102
- `${TAG52} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
27513
+ `${TAG53} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
27103
27514
  );
27104
27515
  return [];
27105
27516
  }
@@ -27107,7 +27518,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
27107
27518
  return Array.isArray(body.writes) ? body.writes : [];
27108
27519
  } catch (err) {
27109
27520
  console.error(
27110
- `${TAG52} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
27521
+ `${TAG53} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
27111
27522
  );
27112
27523
  return [];
27113
27524
  }
@@ -27136,7 +27547,7 @@ function composeInitialMessage(input) {
27136
27547
  }
27137
27548
  async function dispatchReviewer(input, initialMessage) {
27138
27549
  const sessionId = randomUUID16();
27139
- console.log(`${TAG52} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
27550
+ console.log(`${TAG53} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
27140
27551
  const spawned = await managerRcSpawn({
27141
27552
  sessionId,
27142
27553
  initialMessage,
@@ -27156,21 +27567,21 @@ async function firePublicSessionEndReview(input) {
27156
27567
  const dispatchedAt = Date.now();
27157
27568
  if (consumed.has(input.sessionId)) {
27158
27569
  console.log(
27159
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
27570
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
27160
27571
  );
27161
27572
  return;
27162
27573
  }
27163
27574
  const jsonl = await fetchJsonl(input.sessionId);
27164
27575
  if (!jsonl) {
27165
27576
  console.log(
27166
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
27577
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
27167
27578
  );
27168
27579
  return;
27169
27580
  }
27170
27581
  const operatorTurns = countOperatorTurns(jsonl);
27171
27582
  if (operatorTurns === 0) {
27172
27583
  console.log(
27173
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
27584
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
27174
27585
  );
27175
27586
  return;
27176
27587
  }
@@ -27184,19 +27595,19 @@ async function firePublicSessionEndReview(input) {
27184
27595
  });
27185
27596
  if (!consumeOnce(input.sessionId)) {
27186
27597
  console.log(
27187
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
27598
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
27188
27599
  );
27189
27600
  return;
27190
27601
  }
27191
27602
  const dispatched = await dispatchReviewer(input, initialMessage);
27192
27603
  if (!dispatched.ok) {
27193
27604
  console.error(
27194
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
27605
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
27195
27606
  );
27196
27607
  return;
27197
27608
  }
27198
27609
  console.log(
27199
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
27610
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
27200
27611
  );
27201
27612
  }
27202
27613
 
@@ -27330,8 +27741,8 @@ function broadcastAdminShutdown(reason) {
27330
27741
 
27331
27742
  // ../lib/entitlement/src/index.ts
27332
27743
  import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
27333
- import { existsSync as existsSync37, readFileSync as readFileSync40, statSync as statSync16 } from "fs";
27334
- import { resolve as resolve35 } from "path";
27744
+ import { existsSync as existsSync37, readFileSync as readFileSync41, statSync as statSync16 } from "fs";
27745
+ import { resolve as resolve36 } from "path";
27335
27746
 
27336
27747
  // ../lib/entitlement/src/canonicalize.ts
27337
27748
  function canonicalize(value) {
@@ -27366,7 +27777,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
27366
27777
  var GRACE_DAYS = 7;
27367
27778
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
27368
27779
  function pubkeyPath(brand) {
27369
- return resolve35(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
27780
+ return resolve36(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
27370
27781
  }
27371
27782
  var memo = null;
27372
27783
  function memoKey(mtimeMs, account) {
@@ -27378,12 +27789,12 @@ function resolveEntitlement(brand, account) {
27378
27789
  if (brand.commercialMode !== true) {
27379
27790
  return logResolved(implicitTrust(account), null);
27380
27791
  }
27381
- const entitlementPath = resolve35(brand.configDir, "entitlement.json");
27792
+ const entitlementPath = resolve36(brand.configDir, "entitlement.json");
27382
27793
  if (!existsSync37(entitlementPath)) {
27383
27794
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
27384
27795
  }
27385
- const stat8 = statSync16(entitlementPath);
27386
- const key = memoKey(stat8.mtimeMs, account);
27796
+ const stat9 = statSync16(entitlementPath);
27797
+ const key = memoKey(stat9.mtimeMs, account);
27387
27798
  if (memo && memo.key === key) {
27388
27799
  return memo.result;
27389
27800
  }
@@ -27394,7 +27805,7 @@ function resolveEntitlement(brand, account) {
27394
27805
  function verifyAndResolve(brand, entitlementPath, account) {
27395
27806
  let pubkeyPem;
27396
27807
  try {
27397
- pubkeyPem = readFileSync40(pubkeyPath(brand), "utf-8");
27808
+ pubkeyPem = readFileSync41(pubkeyPath(brand), "utf-8");
27398
27809
  } catch (err) {
27399
27810
  return logResolved(anonymousFallback("pubkey-missing"), {
27400
27811
  reason: "pubkey-missing"
@@ -27408,7 +27819,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
27408
27819
  }
27409
27820
  let envelope;
27410
27821
  try {
27411
- envelope = JSON.parse(readFileSync40(entitlementPath, "utf-8"));
27822
+ envelope = JSON.parse(readFileSync41(entitlementPath, "utf-8"));
27412
27823
  } catch {
27413
27824
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
27414
27825
  }
@@ -27570,14 +27981,14 @@ function clientFrom(c) {
27570
27981
  }
27571
27982
  installMediaDownloadGuard();
27572
27983
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
27573
- var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join42(PLATFORM_ROOT8, "config", "brand.json") : "";
27984
+ var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join44(PLATFORM_ROOT8, "config", "brand.json") : "";
27574
27985
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
27575
27986
  if (BRAND_JSON_PATH && !existsSync38(BRAND_JSON_PATH)) {
27576
27987
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
27577
27988
  }
27578
27989
  if (BRAND_JSON_PATH && existsSync38(BRAND_JSON_PATH)) {
27579
27990
  try {
27580
- const parsed = JSON.parse(readFileSync41(BRAND_JSON_PATH, "utf-8"));
27991
+ const parsed = JSON.parse(readFileSync42(BRAND_JSON_PATH, "utf-8"));
27581
27992
  BRAND = { ...BRAND, ...parsed };
27582
27993
  } catch (err) {
27583
27994
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -27609,11 +28020,11 @@ var brandLoginOpts = {
27609
28020
  appleTouchIconPath: brandAppIcon512Path,
27610
28021
  themeColor: BRAND.defaultColors?.primary
27611
28022
  };
27612
- var ALIAS_DOMAINS_PATH = join42(homedir4(), BRAND.configDir, "alias-domains.json");
28023
+ var ALIAS_DOMAINS_PATH = join44(homedir4(), BRAND.configDir, "alias-domains.json");
27613
28024
  function loadAliasDomains() {
27614
28025
  try {
27615
28026
  if (!existsSync38(ALIAS_DOMAINS_PATH)) return null;
27616
- const parsed = JSON.parse(readFileSync41(ALIAS_DOMAINS_PATH, "utf-8"));
28027
+ const parsed = JSON.parse(readFileSync42(ALIAS_DOMAINS_PATH, "utf-8"));
27617
28028
  if (!Array.isArray(parsed)) {
27618
28029
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
27619
28030
  return null;
@@ -27637,7 +28048,15 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
27637
28048
  function isPublicHost(host) {
27638
28049
  return host.startsWith("public.") || aliasDomains.has(host);
27639
28050
  }
27640
- var app63 = new Hono();
28051
+ var app64 = new Hono();
28052
+ function resolveChannelOverride(accountId, channel, senderId) {
28053
+ try {
28054
+ const dir = listValidAccounts().find((a) => a.accountId === accountId)?.accountDir;
28055
+ return dir ? readChannelOverrideId(dir, channel, senderId) : null;
28056
+ } catch {
28057
+ return null;
28058
+ }
28059
+ }
27641
28060
  var nativeFileFollowers = /* @__PURE__ */ new Map();
27642
28061
  var webchatFileFollowers = /* @__PURE__ */ new Map();
27643
28062
  async function fetchAccountStandingRules(accountId) {
@@ -27676,8 +28095,9 @@ async function recordPassiveDispatch(input) {
27676
28095
  }
27677
28096
  var waGateway = new WaGateway({
27678
28097
  fetchStandingRules: fetchAccountStandingRules,
28098
+ resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "whatsapp", senderId),
27679
28099
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
27680
- serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve36(process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
28100
+ serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
27681
28101
  // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
27682
28102
  // path-validation scope) is the sender's effective SESSION account, resolved
27683
28103
  // once at the inbound gate and threaded here via the gateway's per-sender doc
@@ -27692,7 +28112,7 @@ var waGateway = new WaGateway({
27692
28112
  caption,
27693
28113
  accountId,
27694
28114
  maxyAccountId,
27695
- platformRoot: resolve36(process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, ".."))
28115
+ platformRoot: resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, ".."))
27696
28116
  });
27697
28117
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
27698
28118
  },
@@ -27744,7 +28164,17 @@ var waGateway = new WaGateway({
27744
28164
  const managesAccount = effectiveAccountId !== accountId ? effectiveAccountId : "none";
27745
28165
  console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
27746
28166
  }
27747
- const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath });
28167
+ const req = buildWaSpawnRequest({
28168
+ accountId: effectiveAccountId,
28169
+ senderId,
28170
+ role,
28171
+ personId,
28172
+ gatewayUrl,
28173
+ serverPath,
28174
+ // Task 1789 — admin rows only: a public session's id keys on personId as
28175
+ // well as senderId, so the per-sender pointer is not applicable to it.
28176
+ overrideSessionId: role === "admin" ? resolveChannelOverride(effectiveAccountId, "whatsapp", senderId) : null
28177
+ });
27748
28178
  await ensureSessionConversation({
27749
28179
  accountId: effectiveAccountId,
27750
28180
  sessionId: req.sessionId,
@@ -27790,13 +28220,13 @@ var waGateway = new WaGateway({
27790
28220
  nativeFileFollowers.set(senderId, ac);
27791
28221
  }
27792
28222
  });
27793
- app63.route("/", waGateway.routes());
28223
+ app64.route("/", waGateway.routes());
27794
28224
  waGateway.startSweeper();
27795
28225
  function runDuplicateSenderAudit() {
27796
28226
  try {
27797
28227
  const configDir2 = process.env.CLAUDE_CONFIG_DIR;
27798
28228
  if (!configDir2) return;
27799
- const projectsRoot = join42(configDir2, "projects");
28229
+ const projectsRoot = join44(configDir2, "projects");
27800
28230
  const records = [];
27801
28231
  for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
27802
28232
  if (isSubagent || archived) continue;
@@ -27806,6 +28236,8 @@ function runDuplicateSenderAudit() {
27806
28236
  records.push({ sessionId, channel: meta.channel, senderId: meta.senderId, accountId: meta.accountId });
27807
28237
  }
27808
28238
  logDuplicateSenderGroups(findDuplicateSenderGroups(records));
28239
+ const now = Date.now();
28240
+ logUnseatedSenders(waGateway.unseatedSenders(now, 15 * 6e4), now);
27809
28241
  } catch (err) {
27810
28242
  console.error(`[whatsapp-native] op=duplicate-sender-audit-error error=${err instanceof Error ? err.message : String(err)}`);
27811
28243
  }
@@ -27912,16 +28344,26 @@ var webchatGateway = new WebchatGateway({
27912
28344
  firePublicSessionEndReview: (input) => firePublicSessionEndReview(input)
27913
28345
  });
27914
28346
  setWebchatGateway(webchatGateway);
27915
- app63.route("/", webchatGateway.routes());
28347
+ app64.route("/", webchatGateway.routes());
27916
28348
  webchatGateway.startSweeper();
27917
28349
  webchatGateway.startPublicReaper();
27918
28350
  var telegramFileFollowers = /* @__PURE__ */ new Map();
27919
28351
  var telegramGateway = new TelegramGateway({
27920
28352
  fetchStandingRules: fetchAccountStandingRules,
28353
+ resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "telegram", senderId),
27921
28354
  gatewayUrl: telegramGatewayUrl(),
27922
28355
  serverPath: telegramServerPath(),
27923
28356
  ensureChannelSession: async ({ accountId, senderId, role, personId, chatId, gatewayUrl, serverPath }) => {
27924
- const req = buildTelegramSpawnRequest({ accountId, senderId, role, personId, gatewayUrl, serverPath });
28357
+ const req = buildTelegramSpawnRequest({
28358
+ accountId,
28359
+ senderId,
28360
+ role,
28361
+ personId,
28362
+ gatewayUrl,
28363
+ serverPath,
28364
+ // Task 1789 — admin rows only; see the WhatsApp twin.
28365
+ overrideSessionId: role === "admin" ? resolveChannelOverride(accountId, "telegram", senderId) : null
28366
+ });
27925
28367
  const result = await managerRcSpawn(req);
27926
28368
  if ("error" in result) {
27927
28369
  console.error(`[telegram-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
@@ -27946,7 +28388,7 @@ var telegramGateway = new TelegramGateway({
27946
28388
  }
27947
28389
  });
27948
28390
  setTelegramGateway(telegramGateway);
27949
- app63.route("/", telegramGateway.routes());
28391
+ app64.route("/", telegramGateway.routes());
27950
28392
  telegramGateway.startSweeper();
27951
28393
  var chatRoutes = createChatRoutes({
27952
28394
  handleInbound: (input) => webchatGateway.handleInbound(input),
@@ -27980,19 +28422,19 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
27980
28422
  return managed.account;
27981
28423
  }
27982
28424
  });
27983
- app63.route("/api/channel/schedule-inject", scheduleInjectRoutes);
27984
- app63.use("*", clientIpMiddleware);
28425
+ app64.route("/api/channel/schedule-inject", scheduleInjectRoutes);
28426
+ app64.use("*", clientIpMiddleware);
27985
28427
  function allowsSameOriginFraming(path3) {
27986
28428
  return path3 === "/vnc-viewer.html" || path3.startsWith("/api/admin/attachment/") || path3.startsWith("/api/public-reader/attachment/") || path3 === "/api/admin/files/download";
27987
28429
  }
27988
- app63.use("*", async (c, next) => {
28430
+ app64.use("*", async (c, next) => {
27989
28431
  await next();
27990
28432
  c.header("X-Content-Type-Options", "nosniff");
27991
28433
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
27992
28434
  c.header("X-Frame-Options", allowsSameOriginFraming(c.req.path) ? "SAMEORIGIN" : "DENY");
27993
28435
  });
27994
28436
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
27995
- app63.use("*", async (c, next) => {
28437
+ app64.use("*", async (c, next) => {
27996
28438
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
27997
28439
  await next();
27998
28440
  return;
@@ -28010,7 +28452,7 @@ app63.use("*", async (c, next) => {
28010
28452
  });
28011
28453
  }
28012
28454
  });
28013
- app63.use("*", async (c, next) => {
28455
+ app64.use("*", async (c, next) => {
28014
28456
  const host = (c.req.header("host") ?? "").split(":")[0];
28015
28457
  if (isOperatorHost(host, getOperatorDomains()) || !isPublicHost(host)) {
28016
28458
  await next();
@@ -28048,7 +28490,7 @@ function resolveRemoteAuthOpts(c) {
28048
28490
  return { ...brandLoginOpts, origin };
28049
28491
  }
28050
28492
  var MAX_LOGIN_BODY = 8 * 1024;
28051
- app63.post("/__remote-auth/login", async (c) => {
28493
+ app64.post("/__remote-auth/login", async (c) => {
28052
28494
  const client = clientFrom(c);
28053
28495
  const clientIp = client.ip || "unknown";
28054
28496
  if (!requestIsTlsTerminated(c)) {
@@ -28093,7 +28535,7 @@ app63.post("/__remote-auth/login", async (c) => {
28093
28535
  }
28094
28536
  });
28095
28537
  });
28096
- app63.get("/__remote-auth/logout", (c) => {
28538
+ app64.get("/__remote-auth/logout", (c) => {
28097
28539
  const client = clientFrom(c);
28098
28540
  const clientIp = client.ip || "unknown";
28099
28541
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -28106,7 +28548,7 @@ app63.get("/__remote-auth/logout", (c) => {
28106
28548
  }
28107
28549
  });
28108
28550
  });
28109
- app63.post("/__remote-auth/change-password", async (c) => {
28551
+ app64.post("/__remote-auth/change-password", async (c) => {
28110
28552
  const client = clientFrom(c);
28111
28553
  const clientIp = client.ip || "unknown";
28112
28554
  const rateLimited = checkRateLimit(client);
@@ -28165,13 +28607,13 @@ app63.post("/__remote-auth/change-password", async (c) => {
28165
28607
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "change", changeError: "Failed to save password", redirect }), 200);
28166
28608
  }
28167
28609
  });
28168
- app63.get("/__remote-auth/setup", (c) => {
28610
+ app64.get("/__remote-auth/setup", (c) => {
28169
28611
  if (isRemoteAuthConfigured()) {
28170
28612
  return c.redirect("/");
28171
28613
  }
28172
28614
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "setup" }), 200);
28173
28615
  });
28174
- app63.post("/__remote-auth/set-initial-password", async (c) => {
28616
+ app64.post("/__remote-auth/set-initial-password", async (c) => {
28175
28617
  if (isRemoteAuthConfigured()) {
28176
28618
  return c.redirect("/");
28177
28619
  }
@@ -28209,10 +28651,10 @@ app63.post("/__remote-auth/set-initial-password", async (c) => {
28209
28651
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
28210
28652
  }
28211
28653
  });
28212
- app63.get("/api/remote-auth/status", (c) => {
28654
+ app64.get("/api/remote-auth/status", (c) => {
28213
28655
  return c.json({ configured: isRemoteAuthConfigured() });
28214
28656
  });
28215
- app63.post("/api/remote-auth/set-password", async (c) => {
28657
+ app64.post("/api/remote-auth/set-password", async (c) => {
28216
28658
  let body;
28217
28659
  try {
28218
28660
  body = await c.req.json();
@@ -28251,10 +28693,10 @@ app63.post("/api/remote-auth/set-password", async (c) => {
28251
28693
  return c.json({ error: "Failed to save password" }, 500);
28252
28694
  }
28253
28695
  });
28254
- app63.route("/api/_client-error", client_error_default);
28696
+ app64.route("/api/_client-error", client_error_default);
28255
28697
  console.log("[client-error-route] mounted");
28256
28698
  var PWA_PUBLIC_PATHS = /* @__PURE__ */ new Set(["/sw.js", ...PWA_SURFACES.map((s) => s.manifestPath)]);
28257
- app63.use("*", async (c, next) => {
28699
+ app64.use("*", async (c, next) => {
28258
28700
  const host = (c.req.header("host") ?? "").split(":")[0];
28259
28701
  const path3 = c.req.path;
28260
28702
  if (path3 === "/favicon.ico" || path3.startsWith("/assets/") || path3.startsWith("/brand/") || // Public free/busy is read by the booking page (a separate Cloudflare Pages
@@ -28298,27 +28740,28 @@ app63.use("*", async (c, next) => {
28298
28740
  }
28299
28741
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), redirect: path3 }), 200);
28300
28742
  });
28301
- app63.route("/api/health", health_default);
28302
- app63.route("/api/chat", chatRoutes);
28303
- app63.route("/api/whatsapp", whatsapp_default);
28304
- app63.route("/api/storage", storage_broker_default);
28305
- app63.route("/api/whatsapp-reader", whatsapp_reader_default);
28306
- app63.route("/api/public-reader", public_reader_default);
28307
- app63.route("/api/webchat", createWebchatRoutes({
28743
+ app64.route("/api/health", health_default);
28744
+ app64.route("/api/chat", chatRoutes);
28745
+ app64.route("/api/whatsapp", whatsapp_default);
28746
+ app64.route("/api/storage", storage_broker_default);
28747
+ app64.route("/api/whatsapp-reader", whatsapp_reader_default);
28748
+ app64.route("/api/public-reader", public_reader_default);
28749
+ app64.route("/api/webchat", createWebchatRoutes({
28308
28750
  handleInbound: (input) => webchatGateway.handleInbound(input),
28309
28751
  // Task 940 — the permission relay's pointer read + verdict write.
28310
28752
  pendingPromptFor: (key) => webchatGateway.pendingPromptFor(key),
28311
28753
  deliveryFailureFor: (key) => webchatGateway.deliveryFailureFor(key),
28312
28754
  resolvePermissionVerdict: (key, requestId, behavior) => webchatGateway.resolvePermissionVerdict(key, requestId, behavior)
28313
28755
  }));
28314
- app63.route("/api/webchat/greeting", webchat_greeting_default);
28315
- app63.route("/api/telegram", telegram_default);
28316
- app63.route("/api/quickbooks", quickbooks_default);
28317
- app63.route("/api/onboarding", onboarding_default);
28318
- app63.route("/api/admin", admin_default);
28319
- app63.route("/api/access", access_default);
28320
- app63.route("/api/session", session_default2);
28321
- app63.route("/api/calendar", calendar_public_default);
28756
+ app64.route("/api/webchat/greeting", webchat_greeting_default);
28757
+ app64.route("/api/telegram", telegram_default);
28758
+ app64.route("/api/quickbooks", quickbooks_default);
28759
+ app64.route("/api/onboarding", onboarding_default);
28760
+ app64.route("/api/admin", admin_default);
28761
+ app64.route("/api/access", access_default);
28762
+ app64.route("/api/session", session_default2);
28763
+ app64.route("/api/calendar", calendar_public_default);
28764
+ app64.route("/api/portal", portal_fetch_default);
28322
28765
  var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
28323
28766
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
28324
28767
  var IMAGE_MIME = {
@@ -28330,7 +28773,7 @@ var IMAGE_MIME = {
28330
28773
  ".svg": "image/svg+xml",
28331
28774
  ".ico": "image/x-icon"
28332
28775
  };
28333
- app63.get("/agent-assets/:slug/:filename", (c) => {
28776
+ app64.get("/agent-assets/:slug/:filename", (c) => {
28334
28777
  const slug = c.req.param("slug");
28335
28778
  const filename = c.req.param("filename");
28336
28779
  if (!SAFE_SLUG_RE2.test(slug)) {
@@ -28346,8 +28789,8 @@ app63.get("/agent-assets/:slug/:filename", (c) => {
28346
28789
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
28347
28790
  return c.text("Not found", 404);
28348
28791
  }
28349
- const filePath = resolve36(account.accountDir, "agents", slug, "assets", filename);
28350
- const expectedDir = resolve36(account.accountDir, "agents", slug, "assets");
28792
+ const filePath = resolve37(account.accountDir, "agents", slug, "assets", filename);
28793
+ const expectedDir = resolve37(account.accountDir, "agents", slug, "assets");
28351
28794
  if (!filePath.startsWith(expectedDir + "/")) {
28352
28795
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
28353
28796
  return c.text("Forbidden", 403);
@@ -28359,13 +28802,13 @@ app63.get("/agent-assets/:slug/:filename", (c) => {
28359
28802
  const ext = "." + filename.split(".").pop()?.toLowerCase();
28360
28803
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
28361
28804
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
28362
- const body = readFileSync41(filePath);
28805
+ const body = readFileSync42(filePath);
28363
28806
  return c.body(body, 200, {
28364
28807
  "Content-Type": contentType,
28365
28808
  "Cache-Control": "public, max-age=3600"
28366
28809
  });
28367
28810
  });
28368
- app63.get("/generated/:filename", (c) => {
28811
+ app64.get("/generated/:filename", (c) => {
28369
28812
  const filename = c.req.param("filename");
28370
28813
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
28371
28814
  console.error(`[generated] serve file=${filename} status=403`);
@@ -28376,8 +28819,8 @@ app63.get("/generated/:filename", (c) => {
28376
28819
  console.error(`[generated] serve file=${filename} status=404`);
28377
28820
  return c.text("Not found", 404);
28378
28821
  }
28379
- const filePath = resolve36(account.accountDir, "generated", filename);
28380
- const expectedDir = resolve36(account.accountDir, "generated");
28822
+ const filePath = resolve37(account.accountDir, "generated", filename);
28823
+ const expectedDir = resolve37(account.accountDir, "generated");
28381
28824
  if (!filePath.startsWith(expectedDir + "/")) {
28382
28825
  console.error(`[generated] serve file=${filename} status=403`);
28383
28826
  return c.text("Forbidden", 403);
@@ -28389,22 +28832,22 @@ app63.get("/generated/:filename", (c) => {
28389
28832
  const ext = "." + filename.split(".").pop()?.toLowerCase();
28390
28833
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
28391
28834
  console.log(`[generated] serve file=${filename} status=200`);
28392
- const body = readFileSync41(filePath);
28835
+ const body = readFileSync42(filePath);
28393
28836
  return c.body(body, 200, {
28394
28837
  "Content-Type": contentType,
28395
28838
  "Cache-Control": "public, max-age=86400"
28396
28839
  });
28397
28840
  });
28398
- app63.route("/sites", sites_default);
28399
- app63.route("/listings", listings_default);
28400
- app63.route("/v", visitor_event_default);
28401
- app63.route("/v", visitor_consent_default);
28841
+ app64.route("/sites", sites_default);
28842
+ app64.route("/listings", listings_default);
28843
+ app64.route("/v", visitor_event_default);
28844
+ app64.route("/v", visitor_consent_default);
28402
28845
  var htmlCache = /* @__PURE__ */ new Map();
28403
28846
  var brandLogoPath = "/brand/maxy-monochrome.png";
28404
28847
  var brandIconPath = "/brand/maxy-monochrome.png";
28405
28848
  if (BRAND_JSON_PATH && existsSync38(BRAND_JSON_PATH)) {
28406
28849
  try {
28407
- const fullBrand = JSON.parse(readFileSync41(BRAND_JSON_PATH, "utf-8"));
28850
+ const fullBrand = JSON.parse(readFileSync42(BRAND_JSON_PATH, "utf-8"));
28408
28851
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
28409
28852
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
28410
28853
  } catch {
@@ -28431,11 +28874,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
28431
28874
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
28432
28875
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
28433
28876
  var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
28434
- (p) => existsSync38(resolve36(process.cwd(), "public", p.replace(/^\//, "")))
28877
+ (p) => existsSync38(resolve37(process.cwd(), "public", p.replace(/^\//, "")))
28435
28878
  );
28436
28879
  var SW_SOURCE = (() => {
28437
28880
  try {
28438
- return readFileSync41(resolve36(process.cwd(), "public", "sw.js"), "utf-8");
28881
+ return readFileSync42(resolve37(process.cwd(), "public", "sw.js"), "utf-8");
28439
28882
  } catch {
28440
28883
  return null;
28441
28884
  }
@@ -28443,9 +28886,9 @@ var SW_SOURCE = (() => {
28443
28886
  function readInstalledVersion() {
28444
28887
  try {
28445
28888
  if (!PLATFORM_ROOT8) return "unknown";
28446
- const versionFile = join42(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
28889
+ const versionFile = join44(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
28447
28890
  if (!existsSync38(versionFile)) return "unknown";
28448
- const content = readFileSync41(versionFile, "utf-8").trim();
28891
+ const content = readFileSync42(versionFile, "utf-8").trim();
28449
28892
  return content || "unknown";
28450
28893
  } catch {
28451
28894
  return "unknown";
@@ -28486,7 +28929,7 @@ var clientErrorReporterScript = `<script>
28486
28929
  function cachedHtml(file) {
28487
28930
  let html = htmlCache.get(file);
28488
28931
  if (!html) {
28489
- html = readFileSync41(resolve36(process.cwd(), "public", file), "utf-8");
28932
+ html = readFileSync42(resolve37(process.cwd(), "public", file), "utf-8");
28490
28933
  const productNameEsc = escapeHtml(BRAND.productName);
28491
28934
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
28492
28935
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -28503,13 +28946,13 @@ ${clientErrorReporterScript}
28503
28946
  return html;
28504
28947
  }
28505
28948
  function loadBrandingCache(agentSlug) {
28506
- const configDir2 = join42(homedir4(), BRAND.configDir);
28949
+ const configDir2 = join44(homedir4(), BRAND.configDir);
28507
28950
  try {
28508
28951
  const accountId = getDefaultAccountId();
28509
28952
  if (!accountId) return null;
28510
- const cachePath = join42(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
28953
+ const cachePath = join44(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
28511
28954
  if (!existsSync38(cachePath)) return null;
28512
- return JSON.parse(readFileSync41(cachePath, "utf-8"));
28955
+ return JSON.parse(readFileSync42(cachePath, "utf-8"));
28513
28956
  } catch {
28514
28957
  return null;
28515
28958
  }
@@ -28553,7 +28996,7 @@ function brandedPublicHtml(agentSlug) {
28553
28996
  function agentUnavailableHtml() {
28554
28997
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(BRAND.productName)}</title></head><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1.5rem;color:#222"><h1 style="font-size:1.25rem">Agent unavailable</h1><p>This agent isn't available right now. If you reached this page from a saved link, the agent may have been turned off.</p></body></html>`;
28555
28998
  }
28556
- app63.get("/", (c) => {
28999
+ app64.get("/", (c) => {
28557
29000
  const host = (c.req.header("host") ?? "").split(":")[0];
28558
29001
  const klass = classifyHost(host, getOperatorDomains(), isPublicHost);
28559
29002
  if (klass === "operator") {
@@ -28575,12 +29018,12 @@ app63.get("/", (c) => {
28575
29018
  console.log(`[host-class] host=${host} class=admin served=index.html`);
28576
29019
  return c.html(cachedHtml("index.html"));
28577
29020
  });
28578
- app63.get("/public", (c) => {
29021
+ app64.get("/public", (c) => {
28579
29022
  const host = (c.req.header("host") ?? "").split(":")[0];
28580
29023
  if (isPublicHost(host)) return c.text("Not found", 404);
28581
29024
  return c.html(cachedHtml("public.html"));
28582
29025
  });
28583
- app63.get("/public-chat", (c) => {
29026
+ app64.get("/public-chat", (c) => {
28584
29027
  const host = (c.req.header("host") ?? "").split(":")[0];
28585
29028
  if (isPublicHost(host)) return c.text("Not found", 404);
28586
29029
  return c.html(cachedHtml("public.html"));
@@ -28599,12 +29042,12 @@ async function logViewerFetch(c, next) {
28599
29042
  duration_ms: Date.now() - start
28600
29043
  });
28601
29044
  }
28602
- app63.use("/vnc-viewer.html", logViewerFetch);
28603
- app63.use("/vnc-popout.html", logViewerFetch);
28604
- app63.get("/vnc-popout.html", (c) => {
29045
+ app64.use("/vnc-viewer.html", logViewerFetch);
29046
+ app64.use("/vnc-popout.html", logViewerFetch);
29047
+ app64.get("/vnc-popout.html", (c) => {
28605
29048
  let html = htmlCache.get("vnc-popout.html");
28606
29049
  if (!html) {
28607
- html = readFileSync41(resolve36(process.cwd(), "public", "vnc-popout.html"), "utf-8");
29050
+ html = readFileSync42(resolve37(process.cwd(), "public", "vnc-popout.html"), "utf-8");
28608
29051
  const name = escapeHtml(BRAND.productName);
28609
29052
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
28610
29053
  html = html.replace("</head>", ` ${brandScript}
@@ -28614,7 +29057,7 @@ app63.get("/vnc-popout.html", (c) => {
28614
29057
  }
28615
29058
  return c.html(html);
28616
29059
  });
28617
- app63.post("/api/vnc/client-event", async (c) => {
29060
+ app64.post("/api/vnc/client-event", async (c) => {
28618
29061
  let body;
28619
29062
  try {
28620
29063
  body = await c.req.json();
@@ -28635,11 +29078,11 @@ app63.post("/api/vnc/client-event", async (c) => {
28635
29078
  });
28636
29079
  return c.json({ ok: true });
28637
29080
  });
28638
- app63.get("/g/:slug", (c) => {
29081
+ app64.get("/g/:slug", (c) => {
28639
29082
  return c.html(brandedPublicHtml(resolveDefaultSlug() ?? void 0));
28640
29083
  });
28641
29084
  for (const pwa of PWA_SURFACES) {
28642
- app63.get(pwa.manifestPath, (c) => {
29085
+ app64.get(pwa.manifestPath, (c) => {
28643
29086
  const manifest = buildManifest(pwa, {
28644
29087
  productName: BRAND.productName,
28645
29088
  appIcon192: brandAppIcon192Path,
@@ -28655,7 +29098,7 @@ for (const pwa of PWA_SURFACES) {
28655
29098
  return c.body(JSON.stringify(manifest));
28656
29099
  });
28657
29100
  }
28658
- app63.get("/sw.js", (c) => {
29101
+ app64.get("/sw.js", (c) => {
28659
29102
  if (SW_SOURCE == null) {
28660
29103
  console.error("[pwa] op=sw status=500 reason=sw-source-missing");
28661
29104
  return c.text("Service worker unavailable", 500);
@@ -28664,12 +29107,12 @@ app63.get("/sw.js", (c) => {
28664
29107
  console.log(`[pwa] op=sw status=200 ct=${SW_CONTENT_TYPE}`);
28665
29108
  return c.body(SW_SOURCE);
28666
29109
  });
28667
- app63.get("/graph", (c) => {
29110
+ app64.get("/graph", (c) => {
28668
29111
  const host = (c.req.header("host") ?? "").split(":")[0];
28669
29112
  if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
28670
29113
  return c.html(cachedHtml("graph.html"));
28671
29114
  });
28672
- app63.get("/chat", (c) => {
29115
+ app64.get("/chat", (c) => {
28673
29116
  const host = (c.req.header("host") ?? "").split(":")[0];
28674
29117
  if (isOperatorHost(host, getOperatorDomains())) {
28675
29118
  console.log(`[host-class] host=${host} class=operator served=operator.html`);
@@ -28678,32 +29121,32 @@ app63.get("/chat", (c) => {
28678
29121
  if (isPublicHost(host)) return c.text("Not found", 404);
28679
29122
  return c.html(cachedHtml("chat.html"));
28680
29123
  });
28681
- app63.get("/data", (c) => {
29124
+ app64.get("/data", (c) => {
28682
29125
  const host = (c.req.header("host") ?? "").split(":")[0];
28683
29126
  if (isPublicHost(host)) return c.text("Not found", 404);
28684
29127
  return c.html(cachedHtml("data.html"));
28685
29128
  });
28686
- app63.get("/tasks", (c) => {
29129
+ app64.get("/tasks", (c) => {
28687
29130
  const host = (c.req.header("host") ?? "").split(":")[0];
28688
29131
  if (isPublicHost(host)) return c.text("Not found", 404);
28689
29132
  return c.html(cachedHtml("tasks.html"));
28690
29133
  });
28691
- app63.get("/activity", (c) => {
29134
+ app64.get("/activity", (c) => {
28692
29135
  const host = (c.req.header("host") ?? "").split(":")[0];
28693
29136
  if (isPublicHost(host)) return c.text("Not found", 404);
28694
29137
  return c.html(cachedHtml("activity.html"));
28695
29138
  });
28696
- app63.get("/calendar", (c) => {
29139
+ app64.get("/calendar", (c) => {
28697
29140
  const host = (c.req.header("host") ?? "").split(":")[0];
28698
29141
  if (isPublicHost(host)) return c.text("Not found", 404);
28699
29142
  return c.html(cachedHtml("calendar.html"));
28700
29143
  });
28701
- app63.get("/browser", (c) => {
29144
+ app64.get("/browser", (c) => {
28702
29145
  const host = (c.req.header("host") ?? "").split(":")[0];
28703
29146
  if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
28704
29147
  return c.html(cachedHtml("browser.html"));
28705
29148
  });
28706
- app63.get("/:slug", async (c, next) => {
29149
+ app64.get("/:slug", async (c, next) => {
28707
29150
  const slug = c.req.param("slug");
28708
29151
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
28709
29152
  const account = resolveAccount();
@@ -28718,13 +29161,13 @@ app63.get("/:slug", async (c, next) => {
28718
29161
  await next();
28719
29162
  });
28720
29163
  if (brandFaviconPath !== "/favicon.ico") {
28721
- app63.get("/favicon.ico", (c) => {
29164
+ app64.get("/favicon.ico", (c) => {
28722
29165
  c.header("Cache-Control", "public, max-age=300");
28723
29166
  return c.redirect(brandFaviconPath, 302);
28724
29167
  });
28725
29168
  }
28726
- app63.use("/*", serveStatic({ root: "./public" }));
28727
- app63.all("*", (c) => {
29169
+ app64.use("/*", serveStatic({ root: "./public" }));
29170
+ app64.all("*", (c) => {
28728
29171
  const host = (c.req.header("host") ?? "").split(":")[0];
28729
29172
  const path3 = c.req.path;
28730
29173
  if (isPublicHost(host)) {
@@ -28738,11 +29181,11 @@ app63.all("*", (c) => {
28738
29181
  });
28739
29182
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
28740
29183
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
28741
- var httpServer = serve({ fetch: app63.fetch, port, hostname });
29184
+ var httpServer = serve({ fetch: app64.fetch, port, hostname });
28742
29185
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28743
29186
  {
28744
- const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28745
- const reconcileScript = resolve36(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
29187
+ const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29188
+ const reconcileScript = resolve37(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
28746
29189
  const RECONCILE_INTERVAL_MS = 12e4;
28747
29190
  const runReconcile = (ctx) => {
28748
29191
  if (!existsSync38(reconcileScript)) return;
@@ -28767,8 +29210,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28767
29210
  });
28768
29211
  }
28769
29212
  {
28770
- const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28771
- const outlookScript = resolve36(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
29213
+ const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29214
+ const outlookScript = resolve37(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
28772
29215
  const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
28773
29216
  const runOutlookComplete = (ctx) => {
28774
29217
  if (!existsSync38(outlookScript)) return;
@@ -28793,8 +29236,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28793
29236
  });
28794
29237
  }
28795
29238
  {
28796
- const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28797
- const strandedAccountsDir = resolve36(auditRoot, "..", "data/accounts");
29239
+ const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29240
+ const strandedAccountsDir = resolve37(auditRoot, "..", "data/accounts");
28798
29241
  const STRANDED_AUDIT_INTERVAL_MS = 3e5;
28799
29242
  const STRANDED_AGE_MS = 16 * 6e4;
28800
29243
  const STRANDED_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -28804,7 +29247,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28804
29247
  const now = Date.now();
28805
29248
  for (const name of readdirSync26(strandedAccountsDir)) {
28806
29249
  if (!STRANDED_UUID_RE.test(name)) continue;
28807
- const pendingPath2 = resolve36(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
29250
+ const pendingPath2 = resolve37(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
28808
29251
  if (!existsSync38(pendingPath2)) continue;
28809
29252
  const ageMs = now - statSync17(pendingPath2).mtimeMs;
28810
29253
  if (ageMs > STRANDED_AGE_MS) {
@@ -28823,8 +29266,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28823
29266
  });
28824
29267
  }
28825
29268
  {
28826
- const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28827
- const googleAccountsDir = resolve36(googleRoot, "..", "data/accounts");
29269
+ const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29270
+ const googleAccountsDir = resolve37(googleRoot, "..", "data/accounts");
28828
29271
  const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
28829
29272
  const runGooglePendingAuditSafe = () => {
28830
29273
  try {
@@ -28839,7 +29282,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28839
29282
  firstRunDelayMs: 27e3,
28840
29283
  run: runGooglePendingAuditSafe
28841
29284
  });
28842
- const googleAuditScript = resolve36(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
29285
+ const googleAuditScript = resolve37(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
28843
29286
  const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
28844
29287
  const runGoogleAccountAudit = (ctx) => {
28845
29288
  if (!existsSync38(googleAuditScript)) return;
@@ -28867,7 +29310,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28867
29310
  });
28868
29311
  }
28869
29312
  {
28870
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
29313
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
28871
29314
  const STORAGE_AUDIT_INTERVAL_MS = 3e5;
28872
29315
  const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
28873
29316
  console.error(`[storage-audit] error="${err.message}"`);
@@ -28901,8 +29344,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28901
29344
  });
28902
29345
  }
28903
29346
  {
28904
- const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28905
- const publishScript = resolve36(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
29347
+ const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29348
+ const publishScript = resolve37(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
28906
29349
  const PUBLISH_INTERVAL_MS = 3e5;
28907
29350
  const currentAccount = () => {
28908
29351
  try {
@@ -28932,21 +29375,21 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28932
29375
  }
28933
29376
  };
28934
29377
  const auditSnapshotAge = (account) => {
28935
- const availPath = resolve36(account.accountDir, "calendar-availability.json");
29378
+ const availPath = resolve37(account.accountDir, "calendar-availability.json");
28936
29379
  let hasBookingSite = false;
28937
29380
  try {
28938
29381
  if (existsSync38(availPath)) {
28939
- const cfg = JSON.parse(readFileSync41(availPath, "utf-8"));
29382
+ const cfg = JSON.parse(readFileSync42(availPath, "utf-8"));
28940
29383
  hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
28941
29384
  }
28942
29385
  } catch {
28943
29386
  }
28944
29387
  if (!hasBookingSite) return;
28945
- const statePath = resolve36(account.accountDir, "state", "booking-availability", "last-publish.json");
29388
+ const statePath = resolve37(account.accountDir, "state", "booking-availability", "last-publish.json");
28946
29389
  let lastSuccessAt = null;
28947
29390
  if (existsSync38(statePath)) {
28948
29391
  try {
28949
- const rec = JSON.parse(readFileSync41(statePath, "utf-8"));
29392
+ const rec = JSON.parse(readFileSync42(statePath, "utf-8"));
28950
29393
  lastSuccessAt = rec.lastSuccessAt ?? null;
28951
29394
  } catch {
28952
29395
  console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
@@ -28974,8 +29417,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28974
29417
  startTimeEntryCensus(() => getSession());
28975
29418
  startLedgerCensus(() => getSession());
28976
29419
  {
28977
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28978
- const auditScript = resolve36(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
29420
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29421
+ const auditScript = resolve37(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
28979
29422
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
28980
29423
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
28981
29424
  const runConnectorAudit = (ctx) => {
@@ -29047,6 +29490,7 @@ var SUBAPP_MANIFEST = [
29047
29490
  { prefix: "/api/access", file: "server/routes/access/index.ts", subapp: access_default },
29048
29491
  { prefix: "/api/session", file: "server/routes/session.ts", subapp: session_default2 },
29049
29492
  { prefix: "/api/calendar", file: "server/routes/calendar-public.ts", subapp: calendar_public_default },
29493
+ { prefix: "/api/portal", file: "server/routes/portal-fetch.ts", subapp: portal_fetch_default },
29050
29494
  { prefix: "/api/_client-error", file: "server/routes/client-error.ts", subapp: client_error_default },
29051
29495
  { prefix: "/listings", file: "server/routes/listings.ts", subapp: listings_default },
29052
29496
  { prefix: "/v", file: "server/routes/visitor-event.ts", subapp: visitor_event_default },
@@ -29058,7 +29502,7 @@ for (const m of SUBAPP_MANIFEST) {
29058
29502
  }
29059
29503
  try {
29060
29504
  const registered = [];
29061
- for (const r of app63.routes ?? []) {
29505
+ for (const r of app64.routes ?? []) {
29062
29506
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
29063
29507
  if (AGENT_SLUG_PATTERN.test(r.path)) {
29064
29508
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
@@ -29102,7 +29546,7 @@ async function runAdminUserReconcileTick() {
29102
29546
  console.error("[adminuser-self-heal] skip reason=no-users-file");
29103
29547
  return;
29104
29548
  }
29105
- const usersRaw = readFileSync41(USERS_FILE, "utf-8").trim();
29549
+ const usersRaw = readFileSync42(USERS_FILE, "utf-8").trim();
29106
29550
  if (!usersRaw) {
29107
29551
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
29108
29552
  return;
@@ -29150,7 +29594,7 @@ registerLoop({
29150
29594
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
29151
29595
  function countUsersRows() {
29152
29596
  if (!existsSync38(USERS_FILE)) return 0;
29153
- const raw = readFileSync41(USERS_FILE, "utf-8").trim();
29597
+ const raw = readFileSync42(USERS_FILE, "utf-8").trim();
29154
29598
  if (!raw) return 0;
29155
29599
  const users = JSON.parse(raw);
29156
29600
  return users.filter((u) => typeof u.userId === "string").length;
@@ -29283,7 +29727,7 @@ if (bootAccountConfig?.whatsapp) {
29283
29727
  }
29284
29728
  init({
29285
29729
  configDir: configDirForWhatsApp,
29286
- platformRoot: resolve36(process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..")),
29730
+ platformRoot: resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..")),
29287
29731
  accountConfig: bootAccountConfig,
29288
29732
  onMessage: async (msg) => {
29289
29733
  if (msg.isOwnerMirror) {