@rubytech/create-maxy-code 0.1.572 → 0.1.574

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 (423) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +2 -2
  3. package/payload/platform/lib/admins-write/__tests__/index.test.ts +97 -0
  4. package/payload/platform/lib/admins-write/dist/index.d.ts +12 -0
  5. package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/admins-write/dist/index.js +27 -5
  7. package/payload/platform/lib/admins-write/dist/index.js.map +1 -1
  8. package/payload/platform/lib/admins-write/src/index.ts +35 -5
  9. package/payload/platform/lib/admins-write/vitest.config.ts +14 -0
  10. package/payload/platform/lib/dispatch-read/dist/session.d.ts.map +1 -1
  11. package/payload/platform/lib/dispatch-read/dist/session.js +19 -4
  12. package/payload/platform/lib/dispatch-read/dist/session.js.map +1 -1
  13. package/payload/platform/lib/dispatch-read/src/__tests__/session.test.ts +91 -0
  14. package/payload/platform/lib/dispatch-read/src/session.ts +25 -6
  15. package/payload/platform/lib/routine-templates/dist/index.d.ts +2 -2
  16. package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
  17. package/payload/platform/lib/routine-templates/dist/index.js +9 -1
  18. package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
  19. package/payload/platform/lib/routine-templates/src/index.ts +14 -2
  20. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  21. package/payload/platform/plugins/admin/PLUGIN.md +8 -3
  22. package/payload/platform/plugins/admin/hooks/__tests__/html-structure-gate.test.sh +264 -0
  23. package/payload/platform/plugins/admin/hooks/__tests__/quote-render-pdf-conformance.test.sh +122 -24
  24. package/payload/platform/plugins/admin/hooks/__tests__/specialist-dispatch-gate.test.sh +158 -0
  25. package/payload/platform/plugins/admin/hooks/html-structure-gate.sh +165 -0
  26. package/payload/platform/plugins/admin/hooks/quote-render-pdf-conformance.sh +111 -19
  27. package/payload/platform/plugins/admin/hooks/specialist-dispatch-gate.sh +159 -0
  28. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.d.ts +27 -0
  29. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.d.ts.map +1 -0
  30. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.js +29 -0
  31. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.js.map +1 -0
  32. package/payload/platform/plugins/admin/mcp/dist/binding-validate.d.ts +11 -0
  33. package/payload/platform/plugins/admin/mcp/dist/binding-validate.d.ts.map +1 -0
  34. package/payload/platform/plugins/admin/mcp/dist/binding-validate.js +20 -0
  35. package/payload/platform/plugins/admin/mcp/dist/binding-validate.js.map +1 -0
  36. package/payload/platform/plugins/admin/mcp/dist/index.js +137 -2
  37. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  38. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +32 -2
  39. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +192 -90
  40. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +18 -0
  41. package/payload/platform/plugins/docs/references/admin-ui.md +71 -31
  42. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +50 -57
  43. package/payload/platform/plugins/docs/references/internals.md +6 -0
  44. package/payload/platform/plugins/docs/references/outlook-guide.md +31 -1
  45. package/payload/platform/plugins/docs/references/telegram-guide.md +23 -0
  46. package/payload/platform/plugins/docs/references/troubleshooting.md +10 -0
  47. package/payload/platform/plugins/email/PLUGIN.md +1 -1
  48. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js +36 -5
  49. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js.map +1 -1
  50. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +10 -5
  51. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  52. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +12 -6
  53. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  54. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +18 -8
  55. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  56. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.d.ts.map +1 -1
  57. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js +41 -10
  58. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js.map +1 -1
  59. package/payload/platform/plugins/email/references/email-reference.md +12 -0
  60. package/payload/platform/plugins/outlook/PLUGIN.md +3 -1
  61. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.d.ts +2 -0
  62. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.d.ts.map +1 -0
  63. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.js +177 -0
  64. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.js.map +1 -0
  65. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.d.ts +2 -0
  66. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.d.ts.map +1 -0
  67. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.js +130 -0
  68. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.js.map +1 -0
  69. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.d.ts +2 -0
  70. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.d.ts.map +1 -0
  71. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.js +162 -0
  72. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.js.map +1 -0
  73. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.d.ts +2 -0
  74. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.d.ts.map +1 -0
  75. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.js +168 -0
  76. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.js.map +1 -0
  77. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.d.ts +64 -0
  78. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.d.ts.map +1 -0
  79. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.js +110 -0
  80. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.js.map +1 -0
  81. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.d.ts +119 -0
  82. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.d.ts.map +1 -0
  83. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.js +0 -0
  84. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.js.map +1 -0
  85. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.d.ts +8 -0
  86. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.d.ts.map +1 -0
  87. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.js +247 -0
  88. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.js.map +1 -0
  89. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.d.ts.map +1 -1
  90. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js +10 -0
  91. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js.map +1 -1
  92. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
  93. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +21 -0
  94. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -1
  95. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts +16 -0
  96. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -1
  97. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +5 -1
  98. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -1
  99. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +4 -0
  100. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
  101. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +2 -1
  102. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
  103. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts.map +1 -1
  104. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js +24 -0
  105. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js.map +1 -1
  106. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -1
  107. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +2 -1
  108. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -1
  109. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts.map +1 -1
  110. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js +29 -0
  111. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js.map +1 -1
  112. package/payload/platform/plugins/scheduling/PLUGIN.md +4 -0
  113. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.d.ts +2 -0
  114. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.d.ts.map +1 -0
  115. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.js +21 -0
  116. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.js.map +1 -0
  117. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.d.ts +13 -0
  118. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.d.ts.map +1 -0
  119. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.js +10 -0
  120. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.js.map +1 -0
  121. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +54 -0
  122. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  123. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +78 -5
  124. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
  125. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +133 -2
  126. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
  127. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +11 -0
  128. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  129. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +30 -1
  130. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  131. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  132. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +8 -0
  133. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  134. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
  135. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +37 -6
  136. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
  137. package/payload/platform/plugins/telegram/.claude-plugin/plugin.json +1 -1
  138. package/payload/platform/plugins/telegram/PLUGIN.md +14 -2
  139. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.d.ts +2 -0
  140. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.d.ts.map +1 -0
  141. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js +77 -0
  142. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js.map +1 -0
  143. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.d.ts +2 -0
  144. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.d.ts.map +1 -0
  145. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js +49 -0
  146. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js.map +1 -0
  147. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.d.ts +2 -0
  148. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.d.ts.map +1 -0
  149. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.js +25 -0
  150. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.js.map +1 -0
  151. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.d.ts +2 -0
  152. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.d.ts.map +1 -0
  153. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.js +115 -0
  154. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.js.map +1 -0
  155. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.d.ts +2 -0
  156. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.d.ts.map +1 -0
  157. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.js +83 -0
  158. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.js.map +1 -0
  159. package/payload/platform/plugins/telegram/mcp/dist/index.js +42 -0
  160. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  161. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts +32 -0
  162. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts.map +1 -0
  163. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js +59 -0
  164. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js.map +1 -0
  165. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts +18 -0
  166. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts.map +1 -0
  167. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js +35 -0
  168. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js.map +1 -0
  169. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.d.ts +38 -0
  170. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.d.ts.map +1 -0
  171. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.js +21 -0
  172. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.js.map +1 -0
  173. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.d.ts +24 -0
  174. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.d.ts.map +1 -0
  175. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.js +57 -0
  176. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.js.map +1 -0
  177. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.d.ts +16 -0
  178. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.d.ts.map +1 -0
  179. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.js +34 -0
  180. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.js.map +1 -0
  181. package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts +26 -0
  182. package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts.map +1 -0
  183. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js +80 -0
  184. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js.map +1 -0
  185. package/payload/platform/plugins/telegram/skills/build/SKILL.md +110 -0
  186. package/payload/platform/plugins/telegram/skills/build/references/methods.md +219 -0
  187. package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
  188. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.d.ts +32 -0
  189. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.d.ts.map +1 -0
  190. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.js +144 -0
  191. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.js.map +1 -0
  192. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  193. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +3 -0
  194. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  195. package/payload/platform/services/claude-session-manager/dist/html-structure-census.d.ts +44 -0
  196. package/payload/platform/services/claude-session-manager/dist/html-structure-census.d.ts.map +1 -0
  197. package/payload/platform/services/claude-session-manager/dist/html-structure-census.js +185 -0
  198. package/payload/platform/services/claude-session-manager/dist/html-structure-census.js.map +1 -0
  199. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  200. package/payload/platform/services/claude-session-manager/dist/http-server.js +32 -5
  201. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  202. package/payload/platform/services/claude-session-manager/dist/index.js +125 -0
  203. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  204. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +12 -1
  205. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  206. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +13 -1
  207. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  208. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.d.ts.map +1 -1
  209. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js +7 -0
  210. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js.map +1 -1
  211. package/payload/platform/services/claude-session-manager/dist/quote-print-census.d.ts +42 -0
  212. package/payload/platform/services/claude-session-manager/dist/quote-print-census.d.ts.map +1 -0
  213. package/payload/platform/services/claude-session-manager/dist/quote-print-census.js +254 -0
  214. package/payload/platform/services/claude-session-manager/dist/quote-print-census.js.map +1 -0
  215. package/payload/platform/services/claude-session-manager/dist/specialist-census.d.ts +92 -0
  216. package/payload/platform/services/claude-session-manager/dist/specialist-census.d.ts.map +1 -0
  217. package/payload/platform/services/claude-session-manager/dist/specialist-census.js +227 -0
  218. package/payload/platform/services/claude-session-manager/dist/specialist-census.js.map +1 -0
  219. package/payload/platform/services/telegram-channel/dist/jsonl.d.ts +8 -0
  220. package/payload/platform/services/telegram-channel/dist/jsonl.d.ts.map +1 -0
  221. package/payload/platform/services/telegram-channel/dist/jsonl.js +42 -0
  222. package/payload/platform/services/telegram-channel/dist/jsonl.js.map +1 -0
  223. package/payload/platform/services/telegram-channel/dist/notification.d.ts +0 -31
  224. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  225. package/payload/platform/services/telegram-channel/dist/notification.js +4 -33
  226. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  227. package/payload/platform/services/telegram-channel/dist/server.d.ts +0 -5
  228. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  229. package/payload/platform/services/telegram-channel/dist/server.js +7 -101
  230. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  231. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +0 -66
  232. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  233. package/payload/platform/services/whatsapp-channel/dist/notification.js +2 -64
  234. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  235. package/payload/platform/services/whatsapp-channel/dist/server.d.ts +0 -12
  236. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  237. package/payload/platform/services/whatsapp-channel/dist/server.js +15 -268
  238. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  239. package/payload/platform/templates/account-settings.json +8 -4
  240. package/payload/server/public/activity.html +6 -6
  241. package/payload/server/public/agents.html +5 -5
  242. package/payload/server/public/assets/{AdminLoginScreens-CMdj1W5p.js → AdminLoginScreens-Dp6k1XgD.js} +1 -1
  243. package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.br +0 -0
  244. package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.gz +0 -0
  245. package/payload/server/public/assets/{AdminShell-CZcaW3BQ.js → AdminShell-D-wB90c3.js} +1 -1
  246. package/payload/server/public/assets/AdminShell-D-wB90c3.js.br +0 -0
  247. package/payload/server/public/assets/AdminShell-D-wB90c3.js.gz +0 -0
  248. package/payload/server/public/assets/{activity-CAhibgb-.js → activity-BJxhblz-.js} +1 -1
  249. package/payload/server/public/assets/activity-BJxhblz-.js.br +0 -0
  250. package/payload/server/public/assets/activity-BJxhblz-.js.gz +0 -0
  251. package/payload/server/public/assets/{admin-xEXv3w9e.js → admin-DzEi8shs.js} +1 -1
  252. package/payload/server/public/assets/admin-DzEi8shs.js.br +0 -0
  253. package/payload/server/public/assets/admin-DzEi8shs.js.gz +0 -0
  254. package/payload/server/public/assets/{agents-CiAmzTaT.js → agents-DTL8kjfD.js} +1 -1
  255. package/payload/server/public/assets/agents-DTL8kjfD.js.br +0 -0
  256. package/payload/server/public/assets/agents-DTL8kjfD.js.gz +0 -0
  257. package/payload/server/public/assets/{browser-D36BiYhS.js → browser-UZp75zhx.js} +1 -1
  258. package/payload/server/public/assets/browser-UZp75zhx.js.br +0 -0
  259. package/payload/server/public/assets/browser-UZp75zhx.js.gz +0 -0
  260. package/payload/server/public/assets/{calendar-CJAM-iXn.js → calendar-Dfo0O2K_.js} +1 -1
  261. package/payload/server/public/assets/calendar-Dfo0O2K_.js.br +0 -0
  262. package/payload/server/public/assets/calendar-Dfo0O2K_.js.gz +0 -0
  263. package/payload/server/public/assets/chat-ByJAvEWZ.js +1 -0
  264. package/payload/server/public/assets/chat-ByJAvEWZ.js.br +0 -0
  265. package/payload/server/public/assets/chat-ByJAvEWZ.js.gz +0 -0
  266. package/payload/server/public/assets/chevron-left-iG7sYNm7.js +1 -0
  267. package/payload/server/public/assets/chevron-left-iG7sYNm7.js.br +0 -0
  268. package/payload/server/public/assets/clock-xSs0UF10.js +1 -0
  269. package/payload/server/public/assets/clock-xSs0UF10.js.br +0 -0
  270. package/payload/server/public/assets/clock-xSs0UF10.js.gz +0 -0
  271. package/payload/server/public/assets/{copy-axBe92iY.js → copy-CGI2jioK.js} +1 -1
  272. package/payload/server/public/assets/copy-CGI2jioK.js.br +0 -0
  273. package/payload/server/public/assets/copy-CGI2jioK.js.gz +0 -0
  274. package/payload/server/public/assets/data-dvwK4BcP.js +1 -0
  275. package/payload/server/public/assets/data-dvwK4BcP.js.br +0 -0
  276. package/payload/server/public/assets/data-dvwK4BcP.js.gz +0 -0
  277. package/payload/server/public/assets/{dispatch-D_i2ko7K.js → dispatch-Dx9-ty1K.js} +1 -1
  278. package/payload/server/public/assets/dispatch-Dx9-ty1K.js.br +0 -0
  279. package/payload/server/public/assets/dispatch-Dx9-ty1K.js.gz +0 -0
  280. package/payload/server/public/assets/{field-DFgkqq1P.js → field-D-P6CsWA.js} +1 -1
  281. package/payload/server/public/assets/field-D-P6CsWA.js.br +0 -0
  282. package/payload/server/public/assets/field-D-P6CsWA.js.gz +0 -0
  283. package/payload/server/public/assets/{file-text-CEFoq0Je.js → file-text-RWintp9q.js} +1 -1
  284. package/payload/server/public/assets/file-text-RWintp9q.js.br +0 -0
  285. package/payload/server/public/assets/file-text-RWintp9q.js.gz +0 -0
  286. package/payload/server/public/assets/{graph-BrpbilNr.js → graph-CpBlfYrJ.js} +1 -1
  287. package/payload/server/public/assets/graph-CpBlfYrJ.js.br +0 -0
  288. package/payload/server/public/assets/graph-CpBlfYrJ.js.gz +0 -0
  289. package/payload/server/public/assets/{graph-labels-BtmcWzo7.js → graph-labels-BOMyPppX.js} +1 -1
  290. package/payload/server/public/assets/graph-labels-BOMyPppX.js.br +0 -0
  291. package/payload/server/public/assets/graph-labels-BOMyPppX.js.gz +0 -0
  292. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css +1 -0
  293. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.br +0 -0
  294. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.gz +0 -0
  295. package/payload/server/public/assets/{operator-Dl7dJt1G.js → operator-DFgW6GiH.js} +1 -1
  296. package/payload/server/public/assets/operator-DFgW6GiH.js.br +0 -0
  297. package/payload/server/public/assets/operator-DFgW6GiH.js.gz +0 -0
  298. package/payload/server/public/assets/{page-nx31Ij0T.js → page-BRShHSH0.js} +1 -1
  299. package/payload/server/public/assets/page-BRShHSH0.js.br +0 -0
  300. package/payload/server/public/assets/page-BRShHSH0.js.gz +0 -0
  301. package/payload/server/public/assets/{page-zC66xzNT.js → page-BbBCu9iP.js} +5 -5
  302. package/payload/server/public/assets/page-BbBCu9iP.js.br +0 -0
  303. package/payload/server/public/assets/page-BbBCu9iP.js.gz +0 -0
  304. package/payload/server/public/assets/play-BJxUaf6-.js +1 -0
  305. package/payload/server/public/assets/play-BJxUaf6-.js.br +0 -0
  306. package/payload/server/public/assets/play-BJxUaf6-.js.gz +0 -0
  307. package/payload/server/public/assets/{public-DEKnH5Q0.js → public-DkObqkzL.js} +1 -1
  308. package/payload/server/public/assets/public-DkObqkzL.js.br +0 -0
  309. package/payload/server/public/assets/public-DkObqkzL.js.gz +0 -0
  310. package/payload/server/public/assets/{rotate-ccw-CBex8gon.js → rotate-ccw-DgMO69nh.js} +1 -1
  311. package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.br +0 -0
  312. package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.gz +0 -0
  313. package/payload/server/public/assets/routines-D9kvg_Xu.js +2 -0
  314. package/payload/server/public/assets/routines-D9kvg_Xu.js.br +0 -0
  315. package/payload/server/public/assets/routines-D9kvg_Xu.js.gz +0 -0
  316. package/payload/server/public/assets/{skills-BmxjPOmq.js → skills-l5I9HZ3D.js} +1 -1
  317. package/payload/server/public/assets/skills-l5I9HZ3D.js.br +0 -0
  318. package/payload/server/public/assets/skills-l5I9HZ3D.js.gz +0 -0
  319. package/payload/server/public/assets/{tasks-BTFYO9vT.js → tasks-C2qJMv6i.js} +1 -1
  320. package/payload/server/public/assets/tasks-C2qJMv6i.js.br +0 -0
  321. package/payload/server/public/assets/tasks-C2qJMv6i.js.gz +0 -0
  322. package/payload/server/public/assets/{triangle-alert-px1YRJJ7.js → triangle-alert-BnC67uSs.js} +1 -1
  323. package/payload/server/public/assets/triangle-alert-BnC67uSs.js.br +0 -0
  324. package/payload/server/public/assets/triangle-alert-BnC67uSs.js.gz +0 -0
  325. package/payload/server/public/assets/{useCopyFeedback-nMXwRcDX.js → useCopyFeedback-1RmgkTFh.js} +1 -1
  326. package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.br +0 -0
  327. package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.gz +0 -0
  328. package/payload/server/public/assets/{useMediaQuery-DjbOTk2X.js → useMediaQuery-BQslddHQ.js} +1 -1
  329. package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.br +0 -0
  330. package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.gz +0 -0
  331. package/payload/server/public/assets/{useVoiceRecorder-Cx7AOmib.js → useVoiceRecorder-BeZNQEC4.js} +1 -1
  332. package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.br +0 -0
  333. package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.gz +0 -0
  334. package/payload/server/public/assets/{wrench-Cexvlx6J.js → wrench-D4ufQG6C.js} +1 -1
  335. package/payload/server/public/assets/wrench-D4ufQG6C.js.br +0 -0
  336. package/payload/server/public/assets/wrench-D4ufQG6C.js.gz +0 -0
  337. package/payload/server/public/browser.html +5 -5
  338. package/payload/server/public/calendar.html +8 -8
  339. package/payload/server/public/chat.html +14 -14
  340. package/payload/server/public/data.html +12 -12
  341. package/payload/server/public/dispatch.html +5 -5
  342. package/payload/server/public/field.html +3 -3
  343. package/payload/server/public/graph.html +10 -10
  344. package/payload/server/public/index.html +14 -14
  345. package/payload/server/public/operator.html +16 -16
  346. package/payload/server/public/public.html +14 -14
  347. package/payload/server/public/routines.html +8 -8
  348. package/payload/server/public/skills.html +6 -6
  349. package/payload/server/public/tasks.html +7 -7
  350. package/payload/server/server.js +1088 -694
  351. package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.br +0 -0
  352. package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.gz +0 -0
  353. package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.br +0 -0
  354. package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.gz +0 -0
  355. package/payload/server/public/assets/activity-CAhibgb-.js.br +0 -0
  356. package/payload/server/public/assets/activity-CAhibgb-.js.gz +0 -0
  357. package/payload/server/public/assets/admin-xEXv3w9e.js.br +0 -0
  358. package/payload/server/public/assets/admin-xEXv3w9e.js.gz +0 -0
  359. package/payload/server/public/assets/agents-CiAmzTaT.js.br +0 -0
  360. package/payload/server/public/assets/agents-CiAmzTaT.js.gz +0 -0
  361. package/payload/server/public/assets/browser-D36BiYhS.js.br +0 -0
  362. package/payload/server/public/assets/browser-D36BiYhS.js.gz +0 -0
  363. package/payload/server/public/assets/calendar-CJAM-iXn.js.br +0 -0
  364. package/payload/server/public/assets/calendar-CJAM-iXn.js.gz +0 -0
  365. package/payload/server/public/assets/chat-DhZSvtZK.js +0 -1
  366. package/payload/server/public/assets/chat-DhZSvtZK.js.br +0 -0
  367. package/payload/server/public/assets/chat-DhZSvtZK.js.gz +0 -0
  368. package/payload/server/public/assets/chevron-left-CXEcNlc3.js +0 -1
  369. package/payload/server/public/assets/chevron-left-CXEcNlc3.js.br +0 -0
  370. package/payload/server/public/assets/clock-DdxIJwTr.js +0 -1
  371. package/payload/server/public/assets/clock-DdxIJwTr.js.br +0 -0
  372. package/payload/server/public/assets/clock-DdxIJwTr.js.gz +0 -0
  373. package/payload/server/public/assets/copy-axBe92iY.js.br +0 -0
  374. package/payload/server/public/assets/copy-axBe92iY.js.gz +0 -0
  375. package/payload/server/public/assets/data-DUvkI-zq.js +0 -1
  376. package/payload/server/public/assets/data-DUvkI-zq.js.br +0 -0
  377. package/payload/server/public/assets/data-DUvkI-zq.js.gz +0 -0
  378. package/payload/server/public/assets/dispatch-D_i2ko7K.js.br +0 -0
  379. package/payload/server/public/assets/dispatch-D_i2ko7K.js.gz +0 -0
  380. package/payload/server/public/assets/field-DFgkqq1P.js.br +0 -0
  381. package/payload/server/public/assets/field-DFgkqq1P.js.gz +0 -0
  382. package/payload/server/public/assets/file-text-CEFoq0Je.js.br +0 -0
  383. package/payload/server/public/assets/file-text-CEFoq0Je.js.gz +0 -0
  384. package/payload/server/public/assets/graph-BrpbilNr.js.br +0 -0
  385. package/payload/server/public/assets/graph-BrpbilNr.js.gz +0 -0
  386. package/payload/server/public/assets/graph-labels-BtmcWzo7.js.br +0 -0
  387. package/payload/server/public/assets/graph-labels-BtmcWzo7.js.gz +0 -0
  388. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css +0 -1
  389. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.br +0 -0
  390. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.gz +0 -0
  391. package/payload/server/public/assets/operator-Dl7dJt1G.js.br +0 -3
  392. package/payload/server/public/assets/operator-Dl7dJt1G.js.gz +0 -0
  393. package/payload/server/public/assets/page-nx31Ij0T.js.br +0 -0
  394. package/payload/server/public/assets/page-nx31Ij0T.js.gz +0 -0
  395. package/payload/server/public/assets/page-zC66xzNT.js.br +0 -0
  396. package/payload/server/public/assets/page-zC66xzNT.js.gz +0 -0
  397. package/payload/server/public/assets/play-MbU8O2iC.js +0 -1
  398. package/payload/server/public/assets/play-MbU8O2iC.js.br +0 -0
  399. package/payload/server/public/assets/play-MbU8O2iC.js.gz +0 -0
  400. package/payload/server/public/assets/public-DEKnH5Q0.js.br +0 -0
  401. package/payload/server/public/assets/public-DEKnH5Q0.js.gz +0 -0
  402. package/payload/server/public/assets/rotate-ccw-CBex8gon.js.br +0 -0
  403. package/payload/server/public/assets/rotate-ccw-CBex8gon.js.gz +0 -0
  404. package/payload/server/public/assets/routines-BqCmbvFJ.js +0 -2
  405. package/payload/server/public/assets/routines-BqCmbvFJ.js.br +0 -0
  406. package/payload/server/public/assets/routines-BqCmbvFJ.js.gz +0 -0
  407. package/payload/server/public/assets/skills-BmxjPOmq.js.br +0 -0
  408. package/payload/server/public/assets/skills-BmxjPOmq.js.gz +0 -0
  409. package/payload/server/public/assets/tasks-BTFYO9vT.js.br +0 -0
  410. package/payload/server/public/assets/tasks-BTFYO9vT.js.gz +0 -0
  411. package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.br +0 -0
  412. package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.gz +0 -0
  413. package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.br +0 -0
  414. package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.gz +0 -0
  415. package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.br +0 -0
  416. package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.gz +0 -0
  417. package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.br +0 -0
  418. package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.gz +0 -0
  419. package/payload/server/public/assets/wrench-Cexvlx6J.js.br +0 -0
  420. package/payload/server/public/assets/wrench-Cexvlx6J.js.gz +0 -0
  421. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js → jsx-runtime-5lmxFBIw.js} +0 -0
  422. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.br → jsx-runtime-5lmxFBIw.js.br} +0 -0
  423. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.gz → jsx-runtime-5lmxFBIw.js.gz} +0 -0
@@ -340,26 +340,26 @@ function keyOf(resource, accountId) {
340
340
  function generationOf(resource, key2) {
341
341
  return (keyGenerations.get(key2) ?? 0) + (resourceGenerations.get(resource) ?? 0);
342
342
  }
343
- function recomputeFloorMs(entry) {
343
+ function recomputeFloorMs(entry2) {
344
344
  return Math.min(
345
- Math.max(entry.computeMs * RECOMPUTE_FLOOR_FACTOR, MIN_RECOMPUTE_FLOOR_MS),
345
+ Math.max(entry2.computeMs * RECOMPUTE_FLOOR_FACTOR, MIN_RECOMPUTE_FLOOR_MS),
346
346
  MAX_RECOMPUTE_FLOOR_MS
347
347
  );
348
348
  }
349
349
  async function getOrCompute(resource, accountId, compute) {
350
350
  const key2 = keyOf(resource, accountId);
351
- const entry = entries.get(key2);
352
- if (entry !== void 0) {
353
- const age = Date.now() - entry.computedAt;
354
- if (!entry.dirty && age <= BACKSTOP_MS) {
355
- return { value: entry.value, state: "hit" };
351
+ const entry2 = entries.get(key2);
352
+ if (entry2 !== void 0) {
353
+ const age = Date.now() - entry2.computedAt;
354
+ if (!entry2.dirty && age <= BACKSTOP_MS) {
355
+ return { value: entry2.value, state: "hit" };
356
356
  }
357
- const sinceAttempt = Date.now() - entry.lastAttemptAt;
358
- if (entry.dirty && sinceAttempt < recomputeFloorMs(entry) && age <= BACKSTOP_MS) {
359
- return { value: entry.value, state: "dirty-floor" };
357
+ const sinceAttempt = Date.now() - entry2.lastAttemptAt;
358
+ if (entry2.dirty && sinceAttempt < recomputeFloorMs(entry2) && age <= BACKSTOP_MS) {
359
+ return { value: entry2.value, state: "dirty-floor" };
360
360
  }
361
361
  }
362
- const state = entry === void 0 ? "miss" : "stale-backstop";
362
+ const state = entry2 === void 0 ? "miss" : "stale-backstop";
363
363
  const existing = inFlight.get(key2);
364
364
  if (existing) return { value: await existing, state };
365
365
  const startedGeneration = generationOf(resource, key2);
@@ -404,8 +404,8 @@ function invalidate(resource, accountId, source = "action") {
404
404
  entries.delete(key2);
405
405
  return;
406
406
  }
407
- const entry = entries.get(key2);
408
- if (entry) entry.dirty = true;
407
+ const entry2 = entries.get(key2);
408
+ if (entry2) entry2.dirty = true;
409
409
  };
410
410
  if (accountId === null) {
411
411
  resourceGenerations.set(resource, (resourceGenerations.get(resource) ?? 0) + 1);
@@ -974,7 +974,7 @@ function parseTitleCache(raw) {
974
974
  }
975
975
  function serializeTitleCache(cache2) {
976
976
  const obj = {};
977
- for (const [sessionId, entry] of cache2) obj[sessionId] = entry;
977
+ for (const [sessionId, entry2] of cache2) obj[sessionId] = entry2;
978
978
  return JSON.stringify(obj);
979
979
  }
980
980
  async function loadTitleCacheOutcome(accountDir) {
@@ -1007,7 +1007,7 @@ async function saveTitleCacheMerged(accountDir, updates, evict) {
1007
1007
  if (err.code !== "ENOENT") throw err;
1008
1008
  merged = /* @__PURE__ */ new Map();
1009
1009
  }
1010
- for (const [sessionId, entry] of updates) merged.set(sessionId, entry);
1010
+ for (const [sessionId, entry2] of updates) merged.set(sessionId, entry2);
1011
1011
  let evicted = 0;
1012
1012
  if (evict !== void 0) {
1013
1013
  for (const sessionId of evict) {
@@ -1441,11 +1441,11 @@ function enumerateJsonlsWithSkips(projectsRoot, fileRe = SESSION_ID_RE) {
1441
1441
  skipped += 1;
1442
1442
  continue;
1443
1443
  }
1444
- for (const entry of entries2) {
1445
- if (entry.isFile() && fileRe.test(entry.name)) {
1446
- out.push({ path: join11(slugDir, entry.name), isSubagent: false, archived: false });
1447
- } else if (entry.isDirectory() && entry.name === "subagents") {
1448
- const subDir = join11(slugDir, entry.name);
1444
+ for (const entry2 of entries2) {
1445
+ if (entry2.isFile() && fileRe.test(entry2.name)) {
1446
+ out.push({ path: join11(slugDir, entry2.name), isSubagent: false, archived: false });
1447
+ } else if (entry2.isDirectory() && entry2.name === "subagents") {
1448
+ const subDir = join11(slugDir, entry2.name);
1449
1449
  let subEntries;
1450
1450
  try {
1451
1451
  subEntries = readdirSync5(subDir, { withFileTypes: true });
@@ -1460,8 +1460,8 @@ function enumerateJsonlsWithSkips(projectsRoot, fileRe = SESSION_ID_RE) {
1460
1460
  out.push({ path: join11(subDir, sub.name), isSubagent: true, archived: false });
1461
1461
  }
1462
1462
  }
1463
- } else if (entry.isDirectory() && entry.name === "archive") {
1464
- const archiveDir = join11(slugDir, entry.name);
1463
+ } else if (entry2.isDirectory() && entry2.name === "archive") {
1464
+ const archiveDir = join11(slugDir, entry2.name);
1465
1465
  let archiveEntries;
1466
1466
  try {
1467
1467
  archiveEntries = readdirSync5(archiveDir, { withFileTypes: true });
@@ -2147,7 +2147,7 @@ function createSidecarStore(config) {
2147
2147
  function readAll(sessionsDir, onSkip) {
2148
2148
  let names;
2149
2149
  try {
2150
- names = readdirSync16(sessionsDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name);
2150
+ names = readdirSync16(sessionsDir, { withFileTypes: true }).filter((entry2) => entry2.isFile()).map((entry2) => entry2.name);
2151
2151
  } catch {
2152
2152
  return [];
2153
2153
  }
@@ -2563,7 +2563,7 @@ function createWebchatRoutes(deps) {
2563
2563
  clearSignal = body?.clearSignal === true;
2564
2564
  quotedUuid = typeof body?.quotedUuid === "string" && body.quotedUuid ? body.quotedUuid : void 0;
2565
2565
  }
2566
- let deliveryKey = WEBCHAT_ADMIN_KEY;
2566
+ let deliveryKey;
2567
2567
  let deliverySessionId;
2568
2568
  let deliveryAccountId = accountId;
2569
2569
  const explicitTarget = target !== void 0;
@@ -2606,6 +2606,10 @@ function createWebchatRoutes(deps) {
2606
2606
  deliveryKey = `session:${target}`;
2607
2607
  deliverySessionId = target;
2608
2608
  deliveryAccountId = sessionAccountId ?? accountId;
2609
+ } else {
2610
+ deliverySessionId = adminSessionIdFor(bootstrapAccountId, WEBCHAT_ADMIN_KEY, requesterUserId ?? void 0);
2611
+ deliveryKey = `session:${deliverySessionId}`;
2612
+ deliveryAccountId = bootstrapAccountId;
2609
2613
  }
2610
2614
  if (isMultipart) {
2611
2615
  const kind = files.some((f) => isAudioMime(f.type)) ? "voice" : "files";
@@ -2676,20 +2680,24 @@ ${note}` : note;
2676
2680
  }
2677
2681
  console.log(`[webchat:inbound] op=send-parsed kind=${kind} files=${stored.length} bytes=${Buffer.byteLength(text, "utf8")}`);
2678
2682
  }
2679
- if (deliverySessionId !== void 0 && await managerActivity(deliverySessionId) === "busy") {
2683
+ if (target !== void 0 && await managerActivity(deliverySessionId) === "busy") {
2680
2684
  console.log(`[webchat:inbound] op=queued-busy sessionId=${deliverySessionId.slice(0, 8)} key=${deliveryKey}`);
2681
2685
  }
2682
2686
  const adminUserId = requesterUserId ?? void 0;
2683
2687
  const inboundStartedAt = Date.now();
2684
2688
  const sendId = sendIdFromResolve ?? ++sendSeq;
2685
2689
  const replyContext = resolveQuoted(deliverySessionId, quotedUuid, sendId);
2686
- const inbound = deliverySessionId !== void 0 ? await deps.handleInbound({ role: "admin", accountId: deliveryAccountId, key: deliveryKey, text, sessionId: deliverySessionId, adminUserId, ...replyContext ? { replyContext } : {} }) : await deps.handleInbound({ role: "admin", accountId: bootstrapAccountId, key: deliveryKey, text, adminUserId });
2687
- let turnSessionId = deliverySessionId;
2688
- if (turnSessionId === void 0) {
2689
- const canonAccount = scopeAccountId ? validateAccountId2(scopeAccountId) : null;
2690
- turnSessionId = resolveCanonical(accountId, canonAccount?.accountDir ?? null, requesterUserId, primaryUserId, scopeAccountId, multiAccount).sessionId;
2691
- }
2692
- const keyDisplay2 = deliveryKey.startsWith("session:") ? deliveryKey.slice(0, "session:".length + 8) : WEBCHAT_ADMIN_KEY;
2690
+ const inbound = await deps.handleInbound({
2691
+ role: "admin",
2692
+ accountId: deliveryAccountId,
2693
+ key: deliveryKey,
2694
+ text,
2695
+ sessionId: deliverySessionId,
2696
+ adminUserId,
2697
+ ...replyContext ? { replyContext } : {}
2698
+ });
2699
+ const turnSessionId = deliverySessionId;
2700
+ const keyDisplay2 = deliveryKey.slice(0, "session:".length + 8);
2693
2701
  const baseline = jsonlSizeBytes(locateSession(turnSessionId).projectDir, turnSessionId);
2694
2702
  console.error(
2695
2703
  `[webchat:inbound] op=send-accepted key=${keyDisplay2} sendId=${sendId} baselineBytes=${baseline ?? "absent"} acceptMs=${Date.now() - inboundStartedAt} spawn=${inbound.spawn !== null ? "triggered" : "none"}`
@@ -2975,15 +2983,22 @@ var require_session = __commonJS({
2975
2983
  exports.withSession = withSession3;
2976
2984
  exports.__setDriverForTests = __setDriverForTests;
2977
2985
  var node_fs_1 = __require("fs");
2986
+ var node_path_1 = __require("path");
2978
2987
  var neo4j_driver_1 = __importDefault(__require("neo4j-driver"));
2979
2988
  var driver2 = null;
2980
2989
  function password2() {
2981
2990
  if (process.env.NEO4J_PASSWORD)
2982
2991
  return process.env.NEO4J_PASSWORD;
2983
- const file = process.env.NEO4J_PASSWORD_FILE;
2984
- if (file && (0, node_fs_1.existsSync)(file))
2985
- return (0, node_fs_1.readFileSync)(file, "utf-8").trim();
2986
- throw new Error("[dispatch-read] Neo4j password not found in NEO4J_PASSWORD or NEO4J_PASSWORD_FILE");
2992
+ const root = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT;
2993
+ if (!root) {
2994
+ throw new Error("[dispatch-read] neither MAXY_PLATFORM_ROOT nor PLATFORM_ROOT is set \u2014 cannot locate config/.neo4j-password");
2995
+ }
2996
+ const passwordFile = (0, node_path_1.resolve)(root, "config/.neo4j-password");
2997
+ try {
2998
+ return (0, node_fs_1.readFileSync)(passwordFile, "utf-8").trim();
2999
+ } catch (err) {
3000
+ throw new Error(`[dispatch-read] Neo4j password not found. Expected at ${passwordFile}, or in NEO4J_PASSWORD. Read failed: ${err instanceof Error ? err.message : String(err)}`);
3001
+ }
2987
3002
  }
2988
3003
  function getDriver2() {
2989
3004
  if (driver2)
@@ -5087,10 +5102,10 @@ var require_dist7 = __commonJS({
5087
5102
 
5088
5103
  // server/index.ts
5089
5104
  init_dist();
5090
- import { readFileSync as readFileSync54, existsSync as existsSync56, watchFile, readdirSync as readdirSync32, statSync as statSync30 } from "fs";
5105
+ import { readFileSync as readFileSync55, existsSync as existsSync55, watchFile, readdirSync as readdirSync32, statSync as statSync30 } from "fs";
5091
5106
  import { spawn as spawn3 } from "child_process";
5092
5107
  import { createHash as createHash8 } from "crypto";
5093
- import { resolve as resolve50, join as join63, basename as basename17 } from "path";
5108
+ import { resolve as resolve52, join as join63, basename as basename17 } from "path";
5094
5109
  import { homedir as homedir5 } from "os";
5095
5110
 
5096
5111
  // server/lib/route-cache-watchers.ts
@@ -6341,12 +6356,12 @@ function getLanIp() {
6341
6356
  let fallback = null;
6342
6357
  for (const entries2 of Object.values(nets)) {
6343
6358
  if (!entries2) continue;
6344
- for (const entry of entries2) {
6345
- if (entry.family !== "IPv4" || entry.internal) continue;
6346
- if (entry.address.startsWith("192.168.") || entry.address.startsWith("10.")) {
6347
- return entry.address;
6359
+ for (const entry2 of entries2) {
6360
+ if (entry2.family !== "IPv4" || entry2.internal) continue;
6361
+ if (entry2.address.startsWith("192.168.") || entry2.address.startsWith("10.")) {
6362
+ return entry2.address;
6348
6363
  }
6349
- if (!fallback) fallback = entry.address;
6364
+ if (!fallback) fallback = entry2.address;
6350
6365
  }
6351
6366
  }
6352
6367
  return fallback;
@@ -6650,20 +6665,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
6650
6665
  // server/routes/health.ts
6651
6666
  init_claude_agent();
6652
6667
  function checkPort(port2, timeoutMs = 500) {
6653
- return new Promise((resolve51) => {
6668
+ return new Promise((resolve53) => {
6654
6669
  const socket = createConnection2(port2, "127.0.0.1");
6655
6670
  socket.setTimeout(timeoutMs);
6656
6671
  socket.once("connect", () => {
6657
6672
  socket.destroy();
6658
- resolve51(true);
6673
+ resolve53(true);
6659
6674
  });
6660
6675
  socket.once("error", () => {
6661
6676
  socket.destroy();
6662
- resolve51(false);
6677
+ resolve53(false);
6663
6678
  });
6664
6679
  socket.once("timeout", () => {
6665
6680
  socket.destroy();
6666
- resolve51(false);
6681
+ resolve53(false);
6667
6682
  });
6668
6683
  });
6669
6684
  }
@@ -7296,12 +7311,12 @@ function readConversationSummaries2(accountId) {
7296
7311
  const seen = /* @__PURE__ */ new Set();
7297
7312
  try {
7298
7313
  if (!existsSync3(dir)) return out;
7299
- for (const entry of readdirSync2(dir)) {
7300
- if (!entry.endsWith(".jsonl")) continue;
7301
- const stem = entry.slice(0, -".jsonl".length);
7314
+ for (const entry2 of readdirSync2(dir)) {
7315
+ if (!entry2.endsWith(".jsonl")) continue;
7316
+ const stem = entry2.slice(0, -".jsonl".length);
7302
7317
  if (!isStorableStem(stem)) continue;
7303
7318
  const channelKey = fileStemToChannelKey(stem);
7304
- const file = join7(dir, entry);
7319
+ const file = join7(dir, entry2);
7305
7320
  seen.add(file);
7306
7321
  let size;
7307
7322
  let mtimeMs;
@@ -7640,7 +7655,7 @@ async function defaultResolve(accountId) {
7640
7655
  return resolveSocket2(accountId);
7641
7656
  }
7642
7657
  async function fetchAvatar(accountId, jid, deps = {}) {
7643
- const resolve51 = deps.resolve;
7658
+ const resolve53 = deps.resolve;
7644
7659
  const httpGet = deps.httpGet ?? defaultHttpGet;
7645
7660
  const maxAgeMs = deps.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
7646
7661
  const cached3 = readAvatar(accountId, jid);
@@ -7650,7 +7665,7 @@ async function fetchAvatar(accountId, jid, deps = {}) {
7650
7665
  return "fresh";
7651
7666
  }
7652
7667
  console.log(`${TAG7} op=cache jid=${jid} hit=false ageMs=${ageMs2}`);
7653
- const res = resolve51 ? resolve51(accountId) : await defaultResolve(accountId);
7668
+ const res = resolve53 ? resolve53(accountId) : await defaultResolve(accountId);
7654
7669
  if (!res.ok) {
7655
7670
  console.log(`${TAG7} op=no-socket jid=${jid} account=${accountId.slice(0, 8)} reason=${res.reason}`);
7656
7671
  return "no-socket";
@@ -7772,12 +7787,12 @@ async function sweepAvatars() {
7772
7787
  var TAG9 = "[wa-chatmeta]";
7773
7788
  function diffStoredAgainstLive(stored, live) {
7774
7789
  const out = [];
7775
- for (const entry of stored.values()) {
7776
- if (!entry.isGroup) continue;
7777
- const seen = live.get(entry.jid);
7790
+ for (const entry2 of stored.values()) {
7791
+ if (!entry2.isGroup) continue;
7792
+ const seen = live.get(entry2.jid);
7778
7793
  if (seen === void 0) continue;
7779
- if (seen.name !== entry.name) {
7780
- out.push({ jid: entry.jid, stored: entry.name, live: seen.name, observedAt: seen.observedAt });
7794
+ if (seen.name !== entry2.name) {
7795
+ out.push({ jid: entry2.jid, stored: entry2.name, live: seen.name, observedAt: seen.observedAt });
7781
7796
  }
7782
7797
  }
7783
7798
  return out;
@@ -7793,13 +7808,13 @@ async function sweepChatMetadata() {
7793
7808
  const sock = conn.sock;
7794
7809
  const stored = readChatMetadata(conn.platformAccountId);
7795
7810
  const live = /* @__PURE__ */ new Map();
7796
- for (const entry of stored.values()) {
7797
- if (!entry.isGroup) continue;
7811
+ for (const entry2 of stored.values()) {
7812
+ if (!entry2.isGroup) continue;
7798
7813
  groups += 1;
7799
7814
  try {
7800
- const meta = await sock.groupMetadata(entry.jid);
7815
+ const meta = await sock.groupMetadata(entry2.jid);
7801
7816
  if (typeof meta.subject === "string" && meta.subject.length > 0) {
7802
- live.set(entry.jid, { name: meta.subject, observedAt: (/* @__PURE__ */ new Date()).toISOString() });
7817
+ live.set(entry2.jid, { name: meta.subject, observedAt: (/* @__PURE__ */ new Date()).toISOString() });
7803
7818
  checked2 += 1;
7804
7819
  }
7805
7820
  } catch {
@@ -8177,9 +8192,9 @@ function purgeNonSocketAccountManagers(accountDir, socketOwnerId) {
8177
8192
  if (targetId === socketOwnerId) return { removed: [] };
8178
8193
  const map = wa.accountManagers;
8179
8194
  if (!map || typeof map !== "object" || Array.isArray(map)) return { removed: [] };
8180
- const removed = Object.entries(map).map(([phone, entry]) => ({
8195
+ const removed = Object.entries(map).map(([phone, entry2]) => ({
8181
8196
  phone,
8182
- managesAccount: typeof entry === "string" ? entry : entry && typeof entry === "object" && typeof entry.managesAccount === "string" ? entry.managesAccount : ""
8197
+ managesAccount: typeof entry2 === "string" ? entry2 : entry2 && typeof entry2 === "object" && typeof entry2.managesAccount === "string" ? entry2.managesAccount : ""
8183
8198
  }));
8184
8199
  if (removed.length === 0) return { removed: [] };
8185
8200
  delete wa.accountManagers;
@@ -8482,10 +8497,10 @@ function purgeNonSocketRegisteredParties(accountDir, socketOwnerId) {
8482
8497
  if (targetId === socketOwnerId) return { removed: [] };
8483
8498
  const map = wa.registeredParties;
8484
8499
  if (!map || typeof map !== "object" || Array.isArray(map)) return { removed: [] };
8485
- const removed = Object.entries(map).map(([phone, entry]) => ({
8500
+ const removed = Object.entries(map).map(([phone, entry2]) => ({
8486
8501
  phone,
8487
- account: entry && typeof entry === "object" && typeof entry.account === "string" ? entry.account : "",
8488
- party: entry && typeof entry === "object" && typeof entry.party === "string" ? entry.party : ""
8502
+ account: entry2 && typeof entry2 === "object" && typeof entry2.account === "string" ? entry2.account : "",
8503
+ party: entry2 && typeof entry2 === "object" && typeof entry2.party === "string" ? entry2.party : ""
8489
8504
  }));
8490
8505
  if (removed.length === 0) return { removed: [] };
8491
8506
  delete wa.registeredParties;
@@ -9141,8 +9156,8 @@ async function startLogin(opts) {
9141
9156
  await clearAuth(authDir);
9142
9157
  let resolveCode = null;
9143
9158
  let rejectCode = null;
9144
- const codePromise = new Promise((resolve51, reject) => {
9145
- resolveCode = resolve51;
9159
+ const codePromise = new Promise((resolve53, reject) => {
9160
+ resolveCode = resolve53;
9146
9161
  rejectCode = reject;
9147
9162
  });
9148
9163
  const codeTimer = setTimeout(
@@ -9319,27 +9334,27 @@ function bare(value) {
9319
9334
  function reconcilePersonIdentities(rows) {
9320
9335
  const perAccount = /* @__PURE__ */ new Map();
9321
9336
  for (const row of rows) {
9322
- let entry = perAccount.get(row.accountId);
9323
- if (!entry) {
9324
- entry = { seen: /* @__PURE__ */ new Map(), divergent: 0 };
9325
- perAccount.set(row.accountId, entry);
9337
+ let entry2 = perAccount.get(row.accountId);
9338
+ if (!entry2) {
9339
+ entry2 = { seen: /* @__PURE__ */ new Map(), divergent: 0 };
9340
+ perAccount.set(row.accountId, entry2);
9326
9341
  }
9327
9342
  const p = row.phone ? bare(row.phone) : null;
9328
9343
  const t = row.telephone ? bare(row.telephone) : null;
9329
- if (p && t && p !== t) entry.divergent += 1;
9344
+ if (p && t && p !== t) entry2.divergent += 1;
9330
9345
  const key2 = p ?? t;
9331
- if (key2) entry.seen.set(key2, (entry.seen.get(key2) ?? 0) + 1);
9346
+ if (key2) entry2.seen.set(key2, (entry2.seen.get(key2) ?? 0) + 1);
9332
9347
  }
9333
9348
  const accounts = [];
9334
9349
  let duplicates = 0;
9335
9350
  let divergent = 0;
9336
- for (const [accountId, entry] of perAccount) {
9351
+ for (const [accountId, entry2] of perAccount) {
9337
9352
  let dup = 0;
9338
- for (const count of entry.seen.values()) if (count > 1) dup += count - 1;
9353
+ for (const count of entry2.seen.values()) if (count > 1) dup += count - 1;
9339
9354
  duplicates += dup;
9340
- divergent += entry.divergent;
9341
- if (dup > 0 || entry.divergent > 0) {
9342
- accounts.push({ accountId, duplicates: dup, divergent: entry.divergent });
9355
+ divergent += entry2.divergent;
9356
+ if (dup > 0 || entry2.divergent > 0) {
9357
+ accounts.push({ accountId, duplicates: dup, divergent: entry2.divergent });
9343
9358
  }
9344
9359
  }
9345
9360
  return {
@@ -9362,7 +9377,7 @@ function reconcileSelfPhoneAdmins(input) {
9362
9377
  for (const cred of nonHouse) {
9363
9378
  const selfPhone = cred.selfPhone;
9364
9379
  if (!selfPhone) continue;
9365
- if (input.adminPhones.some((entry) => phonesMatch(entry, selfPhone))) {
9380
+ if (input.adminPhones.some((entry2) => phonesMatch(entry2, selfPhone))) {
9366
9381
  matches.push({ accountId: cred.accountId, phone: selfPhone });
9367
9382
  }
9368
9383
  }
@@ -9771,11 +9786,11 @@ init_src2();
9771
9786
  function buildRegisteredPartyReconcile(params) {
9772
9787
  const rows = [];
9773
9788
  const entries2 = Object.entries(params.registeredParties);
9774
- for (const [phone, entry] of entries2) {
9789
+ for (const [phone, entry2] of entries2) {
9775
9790
  const shadowedBy = isAdminPhone(phone, params.adminPhones) ? "adminPhones" : managedAccountFor(params.accountManagers, phone) ? "accountManagers" : null;
9776
- const unresolved2 = !params.isValidAccount(entry.account);
9791
+ const unresolved2 = !params.isValidAccount(entry2.account);
9777
9792
  if (shadowedBy || unresolved2) {
9778
- rows.push({ phone, account: entry.account, party: entry.party, shadowedBy, unresolved: unresolved2 });
9793
+ rows.push({ phone, account: entry2.account, party: entry2.party, shadowedBy, unresolved: unresolved2 });
9779
9794
  }
9780
9795
  }
9781
9796
  const shadowed = rows.filter((r) => r.shadowedBy !== null).length;
@@ -10185,15 +10200,15 @@ app3.post("/config", async (c) => {
10185
10200
  const agents = [];
10186
10201
  if (existsSync9(agentsDir)) {
10187
10202
  try {
10188
- for (const entry of readdirSync8(agentsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
10189
- if (!entry.isDirectory() || entry.name === "admin") continue;
10190
- const configPath3 = resolve8(agentsDir, entry.name, "config.json");
10203
+ for (const entry2 of readdirSync8(agentsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
10204
+ if (!entry2.isDirectory() || entry2.name === "admin") continue;
10205
+ const configPath3 = resolve8(agentsDir, entry2.name, "config.json");
10191
10206
  if (!existsSync9(configPath3)) continue;
10192
10207
  try {
10193
10208
  const parsed = JSON.parse(readFileSync11(configPath3, "utf-8"));
10194
- agents.push({ slug: entry.name, displayName: parsed.displayName ?? entry.name });
10209
+ agents.push({ slug: entry2.name, displayName: parsed.displayName ?? entry2.name });
10195
10210
  } catch {
10196
- console.error(`${TAG16} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
10211
+ console.error(`${TAG16} config action=list-public-agents error="failed to parse config.json for agent ${entry2.name}" \u2014 skipping`);
10197
10212
  }
10198
10213
  }
10199
10214
  } catch (err) {
@@ -10213,8 +10228,8 @@ app3.post("/config", async (c) => {
10213
10228
  const listDenial = bindingScopeDenial(c, callerAccountId, "list-account-managers");
10214
10229
  if (listDenial) return listDenial;
10215
10230
  const all = readAccountManagers(acct.accountDir);
10216
- const boundAccountOf = (entry) => typeof entry === "string" ? entry : entry.managesAccount;
10217
- const accountManagers = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry]) => boundAccountOf(entry) === callerAccountId));
10231
+ const boundAccountOf = (entry2) => typeof entry2 === "string" ? entry2 : entry2.managesAccount;
10232
+ const accountManagers = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry2]) => boundAccountOf(entry2) === callerAccountId));
10218
10233
  console.error(`${TAG16} config action=list-account-managers caller=${callerAccountId || "none"} houseAdmin=${houseAdmin ? "y" : "n"} count=${Object.keys(accountManagers).length} of=${Object.keys(all).length}`);
10219
10234
  return c.json({ ok: true, accountManagers });
10220
10235
  }
@@ -10252,7 +10267,7 @@ app3.post("/config", async (c) => {
10252
10267
  const listDenial = bindingScopeDenial(c, callerAccountId, "list-registered-parties");
10253
10268
  if (listDenial) return listDenial;
10254
10269
  const all = readRegisteredParties(acct.accountDir);
10255
- const registeredParties = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry]) => entry.account === callerAccountId));
10270
+ const registeredParties = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry2]) => entry2.account === callerAccountId));
10256
10271
  console.error(`${TAG16} config action=list-registered-parties caller=${callerAccountId || "none"} houseAdmin=${houseAdmin ? "y" : "n"} count=${Object.keys(registeredParties).length} of=${Object.keys(all).length}`);
10257
10272
  return c.json({ ok: true, registeredParties });
10258
10273
  }
@@ -12162,10 +12177,10 @@ function readState(platformRoot5) {
12162
12177
  return {};
12163
12178
  }
12164
12179
  }
12165
- function recordSuccess(platformRoot5, accountId, entry) {
12180
+ function recordSuccess(platformRoot5, accountId, entry2) {
12166
12181
  const path = stateFilePath(platformRoot5);
12167
12182
  const state = readState(platformRoot5);
12168
- state[accountId] = entry;
12183
+ state[accountId] = entry2;
12169
12184
  try {
12170
12185
  writeFileSync6(path, JSON.stringify(state, null, 1));
12171
12186
  } catch (err) {
@@ -12439,10 +12454,10 @@ function readVisitsPushState(platformRoot5) {
12439
12454
  return {};
12440
12455
  }
12441
12456
  }
12442
- function recordVisitsPush(platformRoot5, accountId, entry) {
12457
+ function recordVisitsPush(platformRoot5, accountId, entry2) {
12443
12458
  const path = visitsStateFilePath(platformRoot5);
12444
12459
  const state = readVisitsPushState(platformRoot5);
12445
- state[accountId] = entry;
12460
+ state[accountId] = entry2;
12446
12461
  try {
12447
12462
  writeFileSync7(path, JSON.stringify(state, null, 1));
12448
12463
  } catch (err) {
@@ -13871,8 +13886,8 @@ function listBotEntries(config) {
13871
13886
  function resolveBotEntry(accounts, botId) {
13872
13887
  const hits = [];
13873
13888
  for (const a of accounts) {
13874
- for (const entry of listBotEntries(a.config.telegram)) {
13875
- if (entry.id === botId) hits.push({ accountId: a.accountId, accountDir: a.accountDir, entry });
13889
+ for (const entry2 of listBotEntries(a.config.telegram)) {
13890
+ if (entry2.id === botId) hits.push({ accountId: a.accountId, accountDir: a.accountDir, entry: entry2 });
13876
13891
  }
13877
13892
  }
13878
13893
  if (hits.length === 0) return { kind: "none" };
@@ -14252,6 +14267,12 @@ function pickActivity(entries2, grown, nowMs, concurrencyWindowMs) {
14252
14267
  // server/routes/session-reader.ts
14253
14268
  init_dist();
14254
14269
  var app7 = new Hono();
14270
+ function probeDataRef(path) {
14271
+ const res = resolveDataPath(path);
14272
+ if (!res.ok) {
14273
+ console.error(`[data] op=download-ref-unservable root="data" path="${path}" reason=resolve-failed status=${res.status}`);
14274
+ }
14275
+ }
14255
14276
  function enrichDownloads(turn, scratchpadRoot) {
14256
14277
  if (turn.kind !== "agent-file") return turn;
14257
14278
  let scratchpadReal = null;
@@ -14263,6 +14284,7 @@ function enrichDownloads(turn, scratchpadRoot) {
14263
14284
  const downloads = turn.files.map((f) => {
14264
14285
  const dataRel = relative3(DATA_ROOT, f);
14265
14286
  if (dataRel && !dataRel.startsWith("..") && !isAbsolute2(dataRel)) {
14287
+ probeDataRef(dataRel);
14266
14288
  return { root: "data", path: dataRel };
14267
14289
  }
14268
14290
  if (scratchpadReal) {
@@ -15701,20 +15723,20 @@ function getTelegramGateway() {
15701
15723
 
15702
15724
  // app/lib/telegram/access-control.ts
15703
15725
  function checkTelegramAccess(params) {
15704
- const { senderId, entry } = params;
15705
- if (entry.role === "admin") {
15706
- const adminUsers = entry.adminUsers ?? [];
15726
+ const { senderId, entry: entry2 } = params;
15727
+ if (entry2.role === "admin") {
15728
+ const adminUsers = entry2.adminUsers ?? [];
15707
15729
  if (adminUsers.includes(senderId)) {
15708
15730
  return { allowed: true, reason: "admin-binding", agentType: "admin" };
15709
15731
  }
15710
15732
  return { allowed: false, reason: "not-admin-user", agentType: "admin" };
15711
15733
  }
15712
- const policy = entry.dmPolicy ?? "disabled";
15734
+ const policy = entry2.dmPolicy ?? "disabled";
15713
15735
  switch (policy) {
15714
15736
  case "open":
15715
15737
  return { allowed: true, reason: "dm-policy-open", agentType: "public" };
15716
15738
  case "allowlist": {
15717
- const allowFrom = entry.allowFrom ?? [];
15739
+ const allowFrom = entry2.allowFrom ?? [];
15718
15740
  if (allowFrom.includes(senderId)) {
15719
15741
  return { allowed: true, reason: "allowlist-match", agentType: "public" };
15720
15742
  }
@@ -16224,8 +16246,8 @@ var NOTIFY_MAX = 5e3;
16224
16246
  var cache = /* @__PURE__ */ new Map();
16225
16247
  function isTelegramNotifyDuplicate(notifyId) {
16226
16248
  const now = Date.now();
16227
- const entry = cache.get(notifyId);
16228
- if (entry && now - entry.ts <= NOTIFY_TTL_MS) return true;
16249
+ const entry2 = cache.get(notifyId);
16250
+ if (entry2 && now - entry2.ts <= NOTIFY_TTL_MS) return true;
16229
16251
  for (const [key2, e] of cache) {
16230
16252
  if (now - e.ts > NOTIFY_TTL_MS) cache.delete(key2);
16231
16253
  }
@@ -16324,9 +16346,9 @@ app11.post("/", async (c) => {
16324
16346
  console.error(`${TAG26} op=reject reason=duplicate-bot-id botId=${botId} accounts=${resolved.count}`);
16325
16347
  return c.json({ ok: false }, 401);
16326
16348
  }
16327
- const { accountId, accountDir, entry } = resolved;
16328
- const agentSlug = entry.role === "admin" ? "admin" : entry.agent;
16329
- console.error(`${TAG26} op=entry botId=${botId} accountId=${accountId} role=${entry.role} agent=${agentSlug}`);
16349
+ const { accountId, accountDir, entry: entry2 } = resolved;
16350
+ const agentSlug = entry2.role === "admin" ? "admin" : entry2.agent;
16351
+ console.error(`${TAG26} op=entry botId=${botId} accountId=${accountId} role=${entry2.role} agent=${agentSlug}`);
16330
16352
  const sp = secretPath(botId);
16331
16353
  if (!existsSync22(sp)) {
16332
16354
  console.error(`${TAG26} op=secret botId=${botId} result=missing-file`);
@@ -16349,7 +16371,7 @@ app11.post("/", async (c) => {
16349
16371
  console.error(
16350
16372
  `${TAG26} op=inbound-kind botId=${botId} update=${shape.envelope} content=${shape.content} isEdit=${shape.isEdit ? "yes" : "no"}`
16351
16373
  );
16352
- const decision = routeTelegramUpdate({ update, entry });
16374
+ const decision = routeTelegramUpdate({ update, entry: entry2 });
16353
16375
  noteInboundKind(shape.primaryKind, decision.kind === "dispatch");
16354
16376
  if (decision.kind === "ignore") {
16355
16377
  console.error(`${TAG26} op=ignore botId=${botId} reason=${decision.reason}`);
@@ -16369,7 +16391,7 @@ app11.post("/", async (c) => {
16369
16391
  if (decision.allowed) {
16370
16392
  const cbKey = telegramChannelKey(botId, decision.senderId);
16371
16393
  const cbAt = (/* @__PURE__ */ new Date()).toISOString();
16372
- const cbAdminUserId = entry.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: decision.senderId }) : null;
16394
+ const cbAdminUserId = entry2.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: decision.senderId }) : null;
16373
16395
  appendMessage2(accountId, {
16374
16396
  messageId: `telegram:${accountId}:${cbKey}:cb:${decision.callbackId}`,
16375
16397
  sessionId: cbAdminUserId ? adminSessionIdFor(accountId, decision.senderId, cbAdminUserId) : adminSessionIdFor(accountId, decision.senderId, void 0, botId),
@@ -16380,13 +16402,13 @@ app11.post("/", async (c) => {
16380
16402
  senderName: decision.senderDisplay,
16381
16403
  chatId: String(decision.chatId),
16382
16404
  channelKey: cbKey,
16383
- scope: entry.role === "admin" ? "admin" : "public",
16405
+ scope: entry2.role === "admin" ? "admin" : "public",
16384
16406
  origin: "inbound",
16385
16407
  createdAt: cbAt
16386
16408
  });
16387
16409
  }
16388
16410
  const answerStartedAt = Date.now();
16389
- const answered3 = await answerTelegramCallback(entry.token, decision.callbackId);
16411
+ const answered3 = await answerTelegramCallback(entry2.token, decision.callbackId);
16390
16412
  const outcome = !answered3.ok ? "error" : decision.allowed ? "stored" : "refused";
16391
16413
  console.error(
16392
16414
  `${TAG26} op=callback-answered botId=${botId} callbackId=${decision.callbackId} ms=${Date.now() - answerStartedAt} outcome=${outcome}${answered3.error ? ` error=${logValue(answered3.error)}` : ""}`
@@ -16396,8 +16418,8 @@ app11.post("/", async (c) => {
16396
16418
  const { senderId, chatId, text } = decision;
16397
16419
  console.error(`${TAG26} op=access botId=${botId} senderId=${senderId} allowed=true reason=${decision.reason}`);
16398
16420
  const channelKey = telegramChannelKey(botId, senderId);
16399
- const scope = entry.role === "admin" ? "admin" : "public";
16400
- const adminUserId = entry.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: senderId }) : null;
16421
+ const scope = entry2.role === "admin" ? "admin" : "public";
16422
+ const adminUserId = entry2.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: senderId }) : null;
16401
16423
  const sessionId = adminUserId ? adminSessionIdFor(accountId, senderId, adminUserId) : adminSessionIdFor(accountId, senderId, void 0, botId);
16402
16424
  const inboundAt = (/* @__PURE__ */ new Date()).toISOString();
16403
16425
  const editSuffix = decision.isEdit ? `:edit:${decision.editDate}` : "";
@@ -16473,7 +16495,7 @@ app11.post("/", async (c) => {
16473
16495
  });
16474
16496
  }
16475
16497
  }
16476
- const active = entry.role === "admin" ? true : isActiveAgentSlug(accountDir, entry.agent);
16498
+ const active = entry2.role === "admin" ? true : isActiveAgentSlug(accountDir, entry2.agent);
16477
16499
  console.error(`${TAG26} op=agent botId=${botId} slug=${agentSlug} active=${active}`);
16478
16500
  if (!active) {
16479
16501
  console.error(`${TAG26} op=reject reason=agent-inactive botId=${botId} agent=${agentSlug}`);
@@ -16485,7 +16507,7 @@ app11.post("/", async (c) => {
16485
16507
  return c.json({ ok: true }, 200);
16486
16508
  }
16487
16509
  const reply = async (replyText) => {
16488
- const sent = await sendTelegramText(entry.token, chatId, replyText);
16510
+ const sent = await sendTelegramText(entry2.token, chatId, replyText);
16489
16511
  if (sent.ok) {
16490
16512
  const at = (/* @__PURE__ */ new Date()).toISOString();
16491
16513
  appendMessage2(accountId, {
@@ -16529,7 +16551,7 @@ app11.post("/", async (c) => {
16529
16551
  });
16530
16552
  if (line) console.error(line);
16531
16553
  }
16532
- const media = await downloadTelegramMedia({ token: entry.token, botId, items: decision.media });
16554
+ const media = await downloadTelegramMedia({ token: entry2.token, botId, items: decision.media });
16533
16555
  void (async () => {
16534
16556
  const heard = await transcribeTelegramAudio({
16535
16557
  botId,
@@ -16541,10 +16563,10 @@ app11.post("/", async (c) => {
16541
16563
  accountId,
16542
16564
  accountDir,
16543
16565
  botId,
16544
- botToken: entry.token,
16566
+ botToken: entry2.token,
16545
16567
  agentSlug,
16546
16568
  senderId,
16547
- role: entry.role,
16569
+ role: entry2.role,
16548
16570
  personId: null,
16549
16571
  // Task 2078 — the admin person this chat is bound to (the users.json
16550
16572
  // telegram binding). It is the SUBJECT of the session id, so one admin's
@@ -16876,16 +16898,27 @@ function writeAdminEntry(input) {
16876
16898
  }
16877
16899
  const rowsBefore = users.map((u) => id8(u.userId)).join(",");
16878
16900
  const existing = users.findIndex((u) => u.userId === input.userId);
16901
+ const pinRotated = existing !== -1 && users[existing].pin !== input.pin;
16902
+ const boundFields = [
16903
+ input.phone !== void 0 ? "phone" : null,
16904
+ input.telegram !== void 0 ? "telegram" : null
16905
+ ].filter(Boolean);
16879
16906
  if (existing === -1) {
16880
- users.push({ userId: input.userId, pin: input.pin });
16907
+ const row = { userId: input.userId, pin: input.pin };
16908
+ if (input.phone !== void 0) row.phone = input.phone;
16909
+ if (input.telegram !== void 0) row.telegram = input.telegram;
16910
+ users.push(row);
16881
16911
  } else {
16882
- users[existing] = { ...users[existing], pin: input.pin };
16912
+ const row = { ...users[existing], pin: input.pin };
16913
+ if (input.phone !== void 0) row.phone = input.phone;
16914
+ if (input.telegram !== void 0) row.telegram = input.telegram;
16915
+ users[existing] = row;
16883
16916
  }
16884
16917
  writeFileAtomic(input.usersFile, JSON.stringify(users, null, 2) + "\n", 384);
16885
16918
  result.usersJsonResult = "ok";
16886
16919
  appendUsersAuditLine(input.audit, {
16887
- action: existing === -1 ? "add" : "set-pin",
16888
- field: existing === -1 ? "row" : "pin",
16920
+ action: existing === -1 ? "add" : pinRotated ? "set-pin" : "set-binding",
16921
+ field: existing === -1 ? "row" : pinRotated ? "pin" : boundFields.join(","),
16889
16922
  rowsBefore,
16890
16923
  rowsAfter: users.map((u) => id8(u.userId)).join(",")
16891
16924
  });
@@ -16942,9 +16975,9 @@ function computeAdminStoreDivergence(input) {
16942
16975
  result.errors.push({ source: input.accountsDir, detail: err instanceof Error ? err.message : String(err) });
16943
16976
  return result;
16944
16977
  }
16945
- for (const entry of entries2) {
16946
- if (entry.startsWith(".")) continue;
16947
- const accountDir = join35(input.accountsDir, entry);
16978
+ for (const entry2 of entries2) {
16979
+ if (entry2.startsWith(".")) continue;
16980
+ const accountDir = join35(input.accountsDir, entry2);
16948
16981
  try {
16949
16982
  if (!statSync18(accountDir).isDirectory()) continue;
16950
16983
  } catch {
@@ -17395,7 +17428,7 @@ var onboarding_default = app13;
17395
17428
 
17396
17429
  // server/routes/dispatch.ts
17397
17430
  init_dist();
17398
- import { resolve as resolve22 } from "path";
17431
+ import { resolve as resolve23 } from "path";
17399
17432
  import { randomUUID as randomUUID5 } from "crypto";
17400
17433
 
17401
17434
  // ../lib/dispatch-read/src/config.ts
@@ -17439,16 +17472,26 @@ function terminalVisitStatuses(cfg) {
17439
17472
  }
17440
17473
 
17441
17474
  // ../lib/dispatch-read/src/session.ts
17442
- import { existsSync as existsSync27, readFileSync as readFileSync32 } from "fs";
17475
+ import { readFileSync as readFileSync32 } from "fs";
17476
+ import { resolve as resolve22 } from "path";
17443
17477
  import neo4j from "neo4j-driver";
17444
17478
  var driver = null;
17445
17479
  function password() {
17446
17480
  if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
17447
- const file = process.env.NEO4J_PASSWORD_FILE;
17448
- if (file && existsSync27(file)) return readFileSync32(file, "utf-8").trim();
17449
- throw new Error(
17450
- "[dispatch-read] Neo4j password not found in NEO4J_PASSWORD or NEO4J_PASSWORD_FILE"
17451
- );
17481
+ const root = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT;
17482
+ if (!root) {
17483
+ throw new Error(
17484
+ "[dispatch-read] neither MAXY_PLATFORM_ROOT nor PLATFORM_ROOT is set \u2014 cannot locate config/.neo4j-password"
17485
+ );
17486
+ }
17487
+ const passwordFile = resolve22(root, "config/.neo4j-password");
17488
+ try {
17489
+ return readFileSync32(passwordFile, "utf-8").trim();
17490
+ } catch (err) {
17491
+ throw new Error(
17492
+ `[dispatch-read] Neo4j password not found. Expected at ${passwordFile}, or in NEO4J_PASSWORD. Read failed: ${err instanceof Error ? err.message : String(err)}`
17493
+ );
17494
+ }
17452
17495
  }
17453
17496
  function getDriver() {
17454
17497
  if (driver) return driver;
@@ -17888,7 +17931,7 @@ function accountFor(cacheKey) {
17888
17931
  if (!cacheKey) return null;
17889
17932
  const accountId = getAccountIdForSession(cacheKey);
17890
17933
  if (!accountId) return null;
17891
- return { accountId, accountDir: resolve22(ACCOUNTS_DIR, accountId) };
17934
+ return { accountId, accountDir: resolve23(ACCOUNTS_DIR, accountId) };
17892
17935
  }
17893
17936
  var app14 = new Hono();
17894
17937
  app14.use("*", requireAdminSession);
@@ -18712,7 +18755,7 @@ init_dist();
18712
18755
  init_paths();
18713
18756
  init_remote_auth();
18714
18757
  init_loop_registry();
18715
- import { appendFileSync as appendFileSync3, existsSync as existsSync28, renameSync as renameSync9, statSync as statSync19 } from "fs";
18758
+ import { appendFileSync as appendFileSync3, existsSync as existsSync27, renameSync as renameSync9, statSync as statSync19 } from "fs";
18716
18759
  import { join as join38 } from "path";
18717
18760
  var CLIENT_ERRORS_LOG = join38(LOG_DIR, "client-errors.log");
18718
18761
  var MAX_LOG_SIZE = 10 * 1024 * 1024;
@@ -18771,7 +18814,7 @@ function stackHead(stack) {
18771
18814
  }
18772
18815
  function rotateIfNeeded() {
18773
18816
  try {
18774
- if (!existsSync28(CLIENT_ERRORS_LOG)) return;
18817
+ if (!existsSync27(CLIENT_ERRORS_LOG)) return;
18775
18818
  const stats = statSync19(CLIENT_ERRORS_LOG);
18776
18819
  if (stats.size < MAX_LOG_SIZE) return;
18777
18820
  renameSync9(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
@@ -18987,15 +19030,15 @@ function operatorRoleFor(config, userId) {
18987
19030
  async function buildAccountOptionList(accounts, userId) {
18988
19031
  const houseAccount = accounts.find((a) => a.config.role === "house");
18989
19032
  console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
18990
- const resolve51 = async (a) => {
19033
+ const resolve53 = async (a) => {
18991
19034
  const businessName = await fetchAccountName(a.accountId) || void 0;
18992
19035
  return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
18993
19036
  };
18994
19037
  const houseOptions = await Promise.all(
18995
- accounts.filter((a) => a.config.role === "house").map(resolve51)
19038
+ accounts.filter((a) => a.config.role === "house").map(resolve53)
18996
19039
  );
18997
19040
  const nonHouseOptions = await Promise.all(
18998
- accounts.filter((a) => a.config.role !== "house").map(resolve51)
19041
+ accounts.filter((a) => a.config.role !== "house").map(resolve53)
18999
19042
  );
19000
19043
  const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
19001
19044
  nonHouseOptions.sort((x, y) => {
@@ -19137,18 +19180,18 @@ var accounts_default = app18;
19137
19180
  init_dist();
19138
19181
  init_paths();
19139
19182
  init_claude_agent();
19140
- import { existsSync as existsSync30, readdirSync as readdirSync20, readFileSync as readFileSync33, statSync as statSync20 } from "fs";
19141
- import { resolve as resolve23, basename as basename11 } from "path";
19183
+ import { existsSync as existsSync29, readdirSync as readdirSync20, readFileSync as readFileSync33, statSync as statSync20 } from "fs";
19184
+ import { resolve as resolve24, basename as basename11 } from "path";
19142
19185
 
19143
19186
  // app/lib/logs-read-resolve.ts
19144
- import { existsSync as existsSync29 } from "fs";
19187
+ import { existsSync as existsSync28 } from "fs";
19145
19188
  import { join as join39 } from "path";
19146
19189
  function resolveSessionLogPaths(filename, logDirs) {
19147
19190
  const tried = [filename];
19148
19191
  const hits = [];
19149
19192
  for (const dir of logDirs) {
19150
19193
  const fullPath = join39(dir, filename);
19151
- if (existsSync29(fullPath)) {
19194
+ if (existsSync28(fullPath)) {
19152
19195
  hits.push({ path: fullPath, dir });
19153
19196
  }
19154
19197
  }
@@ -19166,13 +19209,13 @@ app19.get("/", async (c) => {
19166
19209
  const sessionIdParam = c.req.query("sessionId");
19167
19210
  const cacheKeyParam = c.req.query("cacheKey");
19168
19211
  const download = c.req.query("download") === "1";
19169
- const logDirs = listValidAccounts().slice().sort((a, b) => a.accountId.localeCompare(b.accountId)).map((a) => resolve23(a.accountDir, "logs"));
19212
+ const logDirs = listValidAccounts().slice().sort((a, b) => a.accountId.localeCompare(b.accountId)).map((a) => resolve24(a.accountDir, "logs"));
19170
19213
  logDirs.push(LOG_DIR);
19171
19214
  if (fileParam) {
19172
19215
  const safe = basename11(fileParam);
19173
19216
  const searched = [];
19174
19217
  for (const dir of logDirs) {
19175
- const filePath = resolve23(dir, safe);
19218
+ const filePath = resolve24(dir, safe);
19176
19219
  searched.push(filePath);
19177
19220
  try {
19178
19221
  const buffer = readFileSync33(filePath);
@@ -19275,7 +19318,7 @@ app19.get("/", async (c) => {
19275
19318
  const seen = /* @__PURE__ */ new Set();
19276
19319
  const logs = {};
19277
19320
  for (const dir of logDirs) {
19278
- if (!existsSync30(dir)) continue;
19321
+ if (!existsSync29(dir)) continue;
19279
19322
  let files;
19280
19323
  try {
19281
19324
  files = readdirSync20(dir).filter((f) => f.endsWith(".log"));
@@ -19284,10 +19327,10 @@ app19.get("/", async (c) => {
19284
19327
  console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
19285
19328
  continue;
19286
19329
  }
19287
- files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync20(resolve23(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
19330
+ files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync20(resolve24(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
19288
19331
  seen.add(name);
19289
19332
  try {
19290
- const content = readFileSync33(resolve23(dir, name));
19333
+ const content = readFileSync33(resolve24(dir, name));
19291
19334
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
19292
19335
  logs[name] = tail.trim() || "(empty)";
19293
19336
  } catch (err) {
@@ -19340,12 +19383,12 @@ init_claude_agent();
19340
19383
  init_data_path();
19341
19384
  init_attachments();
19342
19385
  import { readFile as readFile6, readdir as readdir2 } from "fs/promises";
19343
- import { existsSync as existsSync31 } from "fs";
19344
- import { resolve as resolve24 } from "path";
19386
+ import { existsSync as existsSync30 } from "fs";
19387
+ import { resolve as resolve25 } from "path";
19345
19388
  var app21 = new Hono();
19346
19389
  var ATTACHMENT_ID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
19347
19390
  function uploadsDirFor(accountId, attachmentId) {
19348
- return resolve24(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
19391
+ return resolve25(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
19349
19392
  }
19350
19393
  app21.get("/:attachmentId", requireAdminSession, async (c) => {
19351
19394
  const attachmentId = c.req.param("attachmentId");
@@ -19363,12 +19406,12 @@ app21.get("/:attachmentId", requireAdminSession, async (c) => {
19363
19406
  }
19364
19407
  let accountId = pinnedAccountId;
19365
19408
  let dir = uploadsDirFor(accountId, attachmentId);
19366
- let found = existsSync31(dir);
19409
+ let found = existsSync30(dir);
19367
19410
  if (!found) {
19368
19411
  for (const candidate of listValidAccounts()) {
19369
19412
  if (candidate.accountId === pinnedAccountId) continue;
19370
19413
  const candidateDir = uploadsDirFor(candidate.accountId, attachmentId);
19371
- if (existsSync31(candidateDir)) {
19414
+ if (existsSync30(candidateDir)) {
19372
19415
  accountId = candidate.accountId;
19373
19416
  dir = candidateDir;
19374
19417
  found = true;
@@ -19382,8 +19425,8 @@ app21.get("/:attachmentId", requireAdminSession, async (c) => {
19382
19425
  if (!found) {
19383
19426
  return new Response("Not found", { status: 404 });
19384
19427
  }
19385
- const metaPath = resolve24(dir, `${attachmentId}.meta.json`);
19386
- if (!existsSync31(metaPath)) {
19428
+ const metaPath = resolve25(dir, `${attachmentId}.meta.json`);
19429
+ if (!existsSync30(metaPath)) {
19387
19430
  return new Response("Not found", { status: 404 });
19388
19431
  }
19389
19432
  let meta;
@@ -19397,7 +19440,7 @@ app21.get("/:attachmentId", requireAdminSession, async (c) => {
19397
19440
  if (!dataFile) {
19398
19441
  return new Response("Not found", { status: 404 });
19399
19442
  }
19400
- const filePath = resolve24(dir, dataFile);
19443
+ const filePath = resolve25(dir, dataFile);
19401
19444
  const buffer = await readFile6(filePath);
19402
19445
  const inlineSafe = isInlineSafeMime(meta.mimeType);
19403
19446
  const safeName2 = meta.filename.replace(/["\\\r\n]/g, "_");
@@ -19423,7 +19466,7 @@ init_neo4j_store();
19423
19466
  init_data_path();
19424
19467
  init_attachments();
19425
19468
  import { readFile as readFile7 } from "fs/promises";
19426
- import { existsSync as existsSync32, statSync as statSync21 } from "fs";
19469
+ import { existsSync as existsSync31, statSync as statSync21 } from "fs";
19427
19470
  var app22 = new Hono();
19428
19471
  app22.get("/", requireAdminSession, async (c) => {
19429
19472
  const cacheKey = c.var.cacheKey;
@@ -19444,7 +19487,7 @@ app22.get("/", requireAdminSession, async (c) => {
19444
19487
  return c.json({ error: "Not found" }, 404);
19445
19488
  }
19446
19489
  const resolution = resolveClaudeUploadsPath(`${sessionId}/${file}`);
19447
- if (!resolution.ok || !existsSync32(resolution.absolute) || !statSync21(resolution.absolute).isFile()) {
19490
+ if (!resolution.ok || !existsSync31(resolution.absolute) || !statSync21(resolution.absolute).isFile()) {
19448
19491
  console.log(`[session-upload] op=miss session=${short2} file=${file} status=404 reason=absent`);
19449
19492
  return c.json({ error: "Not found" }, 404);
19450
19493
  }
@@ -19470,12 +19513,12 @@ init_helpers();
19470
19513
  init_account_scope();
19471
19514
  init_neo4j_store();
19472
19515
  init_account();
19473
- import { resolve as resolve25 } from "path";
19474
- import { readdirSync as readdirSync22, readFileSync as readFileSync35, existsSync as existsSync34, rmSync as rmSync5, renameSync as renameSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync15, chmodSync } from "fs";
19516
+ import { resolve as resolve26 } from "path";
19517
+ import { readdirSync as readdirSync22, readFileSync as readFileSync35, existsSync as existsSync33, rmSync as rmSync5, renameSync as renameSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync15, chmodSync } from "fs";
19475
19518
  import { execFileSync as execFileSync6 } from "child_process";
19476
19519
 
19477
19520
  // ../services/claude-session-manager/src/tool-surface.ts
19478
- import { readFileSync as readFileSync34, readdirSync as readdirSync21, statSync as statSync22, existsSync as existsSync33 } from "fs";
19521
+ import { readFileSync as readFileSync34, readdirSync as readdirSync21, statSync as statSync22, existsSync as existsSync32 } from "fs";
19479
19522
  import { createHash as createHash5 } from "crypto";
19480
19523
  import { join as join40 } from "path";
19481
19524
 
@@ -19721,7 +19764,7 @@ function parseMcpBlock(frontmatter, pluginDir) {
19721
19764
  function loadToolSurface(pluginsRoots) {
19722
19765
  const roots = Array.isArray(pluginsRoots) ? pluginsRoots : [pluginsRoots];
19723
19766
  for (const r of roots) {
19724
- if (!existsSync33(r)) throw new ToolSurfaceError("plugins-root-missing", r);
19767
+ if (!existsSync32(r)) throw new ToolSurfaceError("plugins-root-missing", r);
19725
19768
  }
19726
19769
  const admin = /* @__PURE__ */ new Set();
19727
19770
  const pub = /* @__PURE__ */ new Set();
@@ -19737,7 +19780,7 @@ function loadToolSurface(pluginsRoots) {
19737
19780
  return;
19738
19781
  }
19739
19782
  const manifestPath = join40(pluginDir, "PLUGIN.md");
19740
- if (!existsSync33(manifestPath)) return;
19783
+ if (!existsSync32(manifestPath)) return;
19741
19784
  const raw = readFileSync34(manifestPath, "utf-8");
19742
19785
  const frontmatter = extractFrontmatter(raw, plugin);
19743
19786
  const tools = parseToolsBlock(frontmatter, plugin);
@@ -19918,7 +19961,7 @@ function agentRiskClass(tools, riskByTool) {
19918
19961
  // server/routes/admin/agents.ts
19919
19962
  var SLUG_UNSAFE = (s) => s.includes("/") || s.includes("..") || s.includes("\\");
19920
19963
  function subdirs(dir) {
19921
- if (!existsSync34(dir)) return [];
19964
+ if (!existsSync33(dir)) return [];
19922
19965
  try {
19923
19966
  return readdirSync22(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
19924
19967
  } catch {
@@ -19939,7 +19982,7 @@ var SPECIALISTS_DIR = ["specialists", "agents"];
19939
19982
  var QUARANTINE_DIR = ["specialists", "agents-disabled"];
19940
19983
  var DISABLED_STORE = "agents-disabled.json";
19941
19984
  function loadRiskSurface() {
19942
- const roots = [resolve25(PLATFORM_ROOT, "plugins"), resolve25(PLATFORM_ROOT, "..", "premium-plugins")].filter((r) => existsSync34(r));
19985
+ const roots = [resolve26(PLATFORM_ROOT, "plugins"), resolve26(PLATFORM_ROOT, "..", "premium-plugins")].filter((r) => existsSync33(r));
19943
19986
  try {
19944
19987
  return { riskByTool: loadToolSurface(roots).riskByTool, failed: null };
19945
19988
  } catch (err) {
@@ -19950,8 +19993,8 @@ function loadRiskSurface() {
19950
19993
  }
19951
19994
  }
19952
19995
  function readDisabledStoreStrict(accountDir) {
19953
- const p = resolve25(accountDir, DISABLED_STORE);
19954
- if (!existsSync34(p)) return /* @__PURE__ */ new Set();
19996
+ const p = resolve26(accountDir, DISABLED_STORE);
19997
+ if (!existsSync33(p)) return /* @__PURE__ */ new Set();
19955
19998
  const parsed = JSON.parse(readFileSync35(p, "utf-8"));
19956
19999
  if (!Array.isArray(parsed.disabled)) throw new Error("disabled is not an array");
19957
20000
  return new Set(parsed.disabled.filter((x) => typeof x === "string"));
@@ -19965,40 +20008,88 @@ function readDisabledStore(accountDir) {
19965
20008
  return /* @__PURE__ */ new Set();
19966
20009
  }
19967
20010
  }
19968
- function authoredAgentFilenames(accountDir) {
19969
- const pluginsDir = resolve25(accountDir, "plugins");
19970
- const names = /* @__PURE__ */ new Set();
20011
+ function authoredAgentFiles(accountDir) {
20012
+ const pluginsDir = resolve26(accountDir, "plugins");
20013
+ const byName = /* @__PURE__ */ new Map();
19971
20014
  for (const plugin of subdirs(pluginsDir)) {
19972
- const dir = resolve25(pluginsDir, plugin, "agents");
19973
- if (!existsSync34(dir)) continue;
20015
+ const dir = resolve26(pluginsDir, plugin, "agents");
20016
+ if (!existsSync33(dir)) continue;
19974
20017
  try {
19975
- for (const entry of readdirSync22(dir)) if (entry.endsWith(".md")) names.add(entry);
20018
+ for (const entry2 of readdirSync22(dir)) {
20019
+ if (!entry2.endsWith(".md")) continue;
20020
+ const path = resolve26(dir, entry2);
20021
+ const paths = byName.get(entry2);
20022
+ if (paths) paths.push(path);
20023
+ else byName.set(entry2, [path]);
20024
+ }
19976
20025
  } catch (err) {
19977
20026
  console.error(`[admin/agents] op=authored-scan read-failed plugin=${plugin} error="${err}"`);
19978
20027
  }
19979
20028
  }
19980
- return names;
20029
+ return byName;
19981
20030
  }
19982
20031
  function shippedSourceDirs(accountDir) {
19983
20032
  return [
19984
- resolve25(accountDir, ...SPECIALISTS_DIR),
19985
- resolve25(accountDir, ...QUARANTINE_DIR),
19986
- resolve25(PLATFORM_ROOT, "templates", "specialists", "agents")
20033
+ resolve26(accountDir, ...SPECIALISTS_DIR),
20034
+ resolve26(accountDir, ...QUARANTINE_DIR),
20035
+ resolve26(PLATFORM_ROOT, "templates", "specialists", "agents")
19987
20036
  ];
19988
20037
  }
20038
+ function readRowFields(path) {
20039
+ const { fm } = splitFrontmatter(readFileSync35(path, "utf-8"));
20040
+ return {
20041
+ name: fm.name ?? "",
20042
+ description: fm.description ?? "",
20043
+ tools: new Set(parseToolsLine(fm.tools))
20044
+ };
20045
+ }
20046
+ function divergentFields(a, b) {
20047
+ const out = [];
20048
+ if (a.name !== b.name) out.push("name");
20049
+ if (a.description !== b.description) out.push("description");
20050
+ if (a.tools.size !== b.tools.size || [...a.tools].some((t) => !b.tools.has(t))) out.push("tools");
20051
+ return out;
20052
+ }
20053
+ function compareToAuthored(candidatePath, authoredPaths, filename) {
20054
+ let candidate;
20055
+ try {
20056
+ candidate = readRowFields(candidatePath);
20057
+ } catch (err) {
20058
+ console.error(`[admin/agents] op=list-shipped-compare-failed file=${filename} side=found reason="${err}"`);
20059
+ return { verdict: "unreadable" };
20060
+ }
20061
+ let firstDivergence = null;
20062
+ let anyUnreadable = false;
20063
+ for (const path of authoredPaths) {
20064
+ let authored;
20065
+ try {
20066
+ authored = readRowFields(path);
20067
+ } catch (err) {
20068
+ console.error(`[admin/agents] op=list-shipped-compare-failed file=${filename} side=authored reason="${err}"`);
20069
+ anyUnreadable = true;
20070
+ continue;
20071
+ }
20072
+ const fields = divergentFields(candidate, authored);
20073
+ if (fields.length === 0) return { verdict: "matched" };
20074
+ firstDivergence ??= { authoredPath: path, fields };
20075
+ }
20076
+ if (firstDivergence) return { verdict: "divergent", ...firstDivergence };
20077
+ if (anyUnreadable) return { verdict: "unreadable" };
20078
+ return { verdict: "unreadable" };
20079
+ }
19989
20080
  function writeDisabledStore(accountDir, disabled) {
19990
- const path = resolve25(accountDir, DISABLED_STORE);
20081
+ const path = resolve26(accountDir, DISABLED_STORE);
19991
20082
  writeFileSync15(path, `${JSON.stringify({ disabled: [...disabled].sort() }, null, 2)}
19992
20083
  `, { mode: 384 });
19993
20084
  chmodSync(path, 384);
19994
20085
  }
19995
- function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, authoredFilenames) {
20086
+ function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, authoredFiles) {
19996
20087
  const dirs = shippedSourceDirs(accountDir);
19997
20088
  const names = /* @__PURE__ */ new Set();
19998
20089
  for (const dir of dirs) {
19999
- if (!existsSync34(dir)) continue;
20090
+ if (!existsSync33(dir)) continue;
20000
20091
  try {
20001
- for (const entry of readdirSync22(dir)) if (entry.endsWith(".md")) names.add(entry);
20092
+ for (const entry2 of readdirSync22(dir)) if (entry2.endsWith(".md")) names.add(entry2);
20002
20093
  } catch (err) {
20003
20094
  console.error(`[admin/agents] op=list-shipped read-failed dir=${dir} error="${err}"`);
20004
20095
  }
@@ -20006,16 +20097,27 @@ function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, author
20006
20097
  const shipped = [];
20007
20098
  let shippedSkipped = 0;
20008
20099
  let deduped = 0;
20100
+ let divergent = 0;
20009
20101
  for (const filename of [...names].sort()) {
20010
- const source = dirs.find((d) => existsSync34(resolve25(d, filename)));
20102
+ const source = dirs.find((d) => existsSync33(resolve26(d, filename)));
20011
20103
  if (!source) continue;
20012
- if ((source === dirs[0] || source === dirs[1]) && authoredFilenames.has(filename)) {
20013
- deduped++;
20014
- console.log(`[admin/agents] op=list-shipped-dedup file=${filename}`);
20015
- continue;
20104
+ const authoredPaths = source === dirs[0] || source === dirs[1] ? authoredFiles.get(filename) : void 0;
20105
+ if (authoredPaths) {
20106
+ const cmp = compareToAuthored(resolve26(source, filename), authoredPaths, filename);
20107
+ if (cmp.verdict === "matched") {
20108
+ deduped++;
20109
+ console.log(`[admin/agents] op=list-shipped-dedup file=${filename}`);
20110
+ continue;
20111
+ }
20112
+ if (cmp.verdict === "divergent") {
20113
+ divergent++;
20114
+ console.log(
20115
+ `[admin/agents] op=list-shipped-divergent file=${filename} found=${resolve26(source, filename)} authored=${cmp.authoredPath} fields=${cmp.fields.join(",")}`
20116
+ );
20117
+ }
20016
20118
  }
20017
20119
  try {
20018
- const { fm } = splitFrontmatter(readFileSync35(resolve25(source, filename), "utf-8"));
20120
+ const { fm } = splitFrontmatter(readFileSync35(resolve26(source, filename), "utf-8"));
20019
20121
  if (!fm.name) {
20020
20122
  shippedSkipped++;
20021
20123
  console.error(`[admin/agents] op=list-shipped-skip file=${filename} reason=no-name`);
@@ -20048,7 +20150,7 @@ function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, author
20048
20150
  console.error(`[admin/agents] op=list-shipped-skip file=${filename} error="${err}"`);
20049
20151
  }
20050
20152
  }
20051
- return { shipped, shippedSkipped, deduped };
20153
+ return { shipped, shippedSkipped, deduped, divergent };
20052
20154
  }
20053
20155
  function parseToolsLine(raw) {
20054
20156
  return raw ? raw.split(",").map((t) => t.trim()).filter(Boolean) : [];
@@ -20069,31 +20171,31 @@ function classify(tools, riskByTool, riskSurfaceFailed) {
20069
20171
  return agentRiskClass(tools, riskByTool);
20070
20172
  }
20071
20173
  function listSpecialists(accountDir, riskByTool, riskSurfaceFailed) {
20072
- const pluginsDir = resolve25(accountDir, "plugins");
20174
+ const pluginsDir = resolve26(accountDir, "plugins");
20073
20175
  const specialists = [];
20074
20176
  let specialistsSkipped = 0;
20075
20177
  for (const plugin of subdirs(pluginsDir).sort()) {
20076
- const agentsDirP = resolve25(pluginsDir, plugin, "agents");
20077
- if (!existsSync34(agentsDirP)) continue;
20178
+ const agentsDirP = resolve26(pluginsDir, plugin, "agents");
20179
+ if (!existsSync33(agentsDirP)) continue;
20078
20180
  let entries2;
20079
20181
  try {
20080
20182
  entries2 = readdirSync22(agentsDirP, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
20081
20183
  } catch {
20082
20184
  continue;
20083
20185
  }
20084
- for (const entry of entries2) {
20085
- if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20186
+ for (const entry2 of entries2) {
20187
+ if (!entry2.isFile() || !entry2.name.endsWith(".md")) continue;
20086
20188
  try {
20087
- const { fm } = splitFrontmatter(readFileSync35(resolve25(agentsDirP, entry.name), "utf-8"));
20189
+ const { fm } = splitFrontmatter(readFileSync35(resolve26(agentsDirP, entry2.name), "utf-8"));
20088
20190
  if (!fm.name) {
20089
20191
  specialistsSkipped++;
20090
- console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry.name}`);
20192
+ console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry2.name}`);
20091
20193
  continue;
20092
20194
  }
20093
20195
  const specialistTools = parseToolsLine(fm.tools);
20094
20196
  const r = classify(specialistTools, riskByTool, riskSurfaceFailed);
20095
20197
  specialists.push({
20096
- slug: entry.name.replace(/\.md$/, ""),
20198
+ slug: entry2.name.replace(/\.md$/, ""),
20097
20199
  displayName: fm.name,
20098
20200
  kind: "specialist",
20099
20201
  origin: "specialist",
@@ -20116,11 +20218,11 @@ function listSpecialists(accountDir, riskByTool, riskSurfaceFailed) {
20116
20218
  // have the surface claim an agent is stopped while it is still being
20117
20219
  // spawned. Absence from the live dir alone is also not the test: a
20118
20220
  // specialist that was never activated was never switched off either.
20119
- disabled: existsSync34(resolve25(accountDir, ...QUARANTINE_DIR, entry.name)) && !existsSync34(resolve25(accountDir, ...SPECIALISTS_DIR, entry.name))
20221
+ disabled: existsSync33(resolve26(accountDir, ...QUARANTINE_DIR, entry2.name)) && !existsSync33(resolve26(accountDir, ...SPECIALISTS_DIR, entry2.name))
20120
20222
  });
20121
20223
  } catch {
20122
20224
  specialistsSkipped++;
20123
- console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry.name}`);
20225
+ console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry2.name}`);
20124
20226
  }
20125
20227
  }
20126
20228
  }
@@ -20143,20 +20245,20 @@ app23.get("/", requireAdminSession, (c) => {
20143
20245
  const disabled = readDisabledStore(account.accountDir);
20144
20246
  const agents = [];
20145
20247
  let skipped = 0;
20146
- const agentsDir = resolve25(account.accountDir, "agents");
20147
- if (existsSync34(agentsDir)) {
20248
+ const agentsDir = resolve26(account.accountDir, "agents");
20249
+ if (existsSync33(agentsDir)) {
20148
20250
  try {
20149
20251
  const entries2 = readdirSync22(agentsDir, { withFileTypes: true });
20150
- for (const entry of entries2.sort((a, b) => a.name.localeCompare(b.name))) {
20151
- if (!entry.isDirectory()) continue;
20152
- if (entry.name === "admin") continue;
20153
- const configPath3 = resolve25(agentsDir, entry.name, "config.json");
20154
- if (!existsSync34(configPath3)) continue;
20252
+ for (const entry2 of entries2.sort((a, b) => a.name.localeCompare(b.name))) {
20253
+ if (!entry2.isDirectory()) continue;
20254
+ if (entry2.name === "admin") continue;
20255
+ const configPath3 = resolve26(agentsDir, entry2.name, "config.json");
20256
+ if (!existsSync33(configPath3)) continue;
20155
20257
  try {
20156
20258
  const config = JSON.parse(readFileSync35(configPath3, "utf-8"));
20157
20259
  agents.push({
20158
- slug: entry.name,
20159
- displayName: config.displayName ?? entry.name,
20260
+ slug: entry2.name,
20261
+ displayName: config.displayName ?? entry2.name,
20160
20262
  status: config.status ?? "unknown",
20161
20263
  kind: "public",
20162
20264
  origin: "public",
@@ -20169,7 +20271,7 @@ app23.get("/", requireAdminSession, (c) => {
20169
20271
  });
20170
20272
  } catch {
20171
20273
  skipped++;
20172
- console.error(`[admin/agents] failed to parse config.json for agent "${entry.name}" \u2014 skipping`);
20274
+ console.error(`[admin/agents] failed to parse config.json for agent "${entry2.name}" \u2014 skipping`);
20173
20275
  }
20174
20276
  }
20175
20277
  } catch (err) {
@@ -20177,12 +20279,12 @@ app23.get("/", requireAdminSession, (c) => {
20177
20279
  }
20178
20280
  }
20179
20281
  const { specialists, specialistsSkipped } = listSpecialists(account.accountDir, riskByTool, riskSurfaceFailed !== null);
20180
- const { shipped, shippedSkipped, deduped } = listShipped(
20282
+ const { shipped, shippedSkipped, deduped, divergent } = listShipped(
20181
20283
  account.accountDir,
20182
20284
  riskByTool,
20183
20285
  disabled,
20184
20286
  riskSurfaceFailed !== null,
20185
- authoredAgentFilenames(account.accountDir)
20287
+ authoredAgentFiles(account.accountDir)
20186
20288
  );
20187
20289
  const rows = [...agents, ...specialists, ...shipped];
20188
20290
  const acct = account.accountId.slice(0, 8);
@@ -20200,17 +20302,26 @@ app23.get("/", requireAdminSession, (c) => {
20200
20302
  console.log(
20201
20303
  `[agents] op=rule-census account=${acct} rules=${COMBINATION_RULES.length} classified=${classified} fired=${firedRules.size} never=${never.length > 0 ? never.join(",") : "none"}`
20202
20304
  );
20203
- console.log(`[admin/agents] op=list accountId=${acct} agents=${agents.length} specialists=${specialists.length} shipped=${shipped.length} skipped=${skipped} specialistsSkipped=${specialistsSkipped} shippedSkipped=${shippedSkipped} deduped=${deduped}`);
20305
+ console.log(`[admin/agents] op=list accountId=${acct} agents=${agents.length} specialists=${specialists.length} shipped=${shipped.length} skipped=${skipped} specialistsSkipped=${specialistsSkipped} shippedSkipped=${shippedSkipped} deduped=${deduped} divergent=${divergent}`);
20204
20306
  return c.json({
20205
20307
  agents: rows,
20206
20308
  accountId: account.accountId,
20207
20309
  skipped,
20208
20310
  specialistsSkipped,
20209
20311
  shippedSkipped,
20210
- // How many live-directory candidates were dropped as authored duplicates.
20211
- // `shipped + shippedSkipped + deduped` equals the number of distinct `.md`
20212
- // names across the three source directories.
20312
+ // How many live-directory or quarantine-directory candidates were dropped as
20313
+ // authored duplicates meaning the two files agree on the three fields the
20314
+ // row shows. `shipped + shippedSkipped + deduped` equals the number of
20315
+ // distinct `.md` names across the three source directories.
20213
20316
  deduped,
20317
+ // How many candidates were KEPT because both files read and the three
20318
+ // fields DISAGREE. Never a fourth term in the identity above: a kept
20319
+ // candidate is parsed like any other, so it lands in `shipped`, or in
20320
+ // `shippedSkipped` when its own frontmatter carries no `name:`. A candidate
20321
+ // that could not be compared at all is not counted here — that is
20322
+ // `op=list-shipped-compare-failed`, and reporting it as drift would claim a
20323
+ // disagreement nobody read.
20324
+ divergent,
20214
20325
  riskSurfaceFailed: riskSurfaceFailed !== null
20215
20326
  });
20216
20327
  });
@@ -20224,9 +20335,9 @@ app23.get("/:slug", requireAdminSession, (c) => {
20224
20335
  if (SLUG_UNSAFE(slug)) return c.json({ error: "Invalid agent slug" }, 400);
20225
20336
  if (c.req.query("origin") === "shipped") {
20226
20337
  const dirs = shippedSourceDirs(account.accountDir);
20227
- const source = dirs.find((d) => existsSync34(resolve25(d, `${slug}.md`)));
20338
+ const source = dirs.find((d) => existsSync33(resolve26(d, `${slug}.md`)));
20228
20339
  if (!source) return c.json({ error: "Agent not found" }, 404);
20229
- const { fm, body } = splitFrontmatter(readFileSync35(resolve25(source, `${slug}.md`), "utf-8"));
20340
+ const { fm, body } = splitFrontmatter(readFileSync35(resolve26(source, `${slug}.md`), "utf-8"));
20230
20341
  const { riskByTool, failed: failed2 } = loadRiskSurface();
20231
20342
  const tools = parseToolsLine(fm.tools);
20232
20343
  const r = classify(tools, riskByTool, failed2 !== null);
@@ -20256,10 +20367,10 @@ app23.get("/:slug", requireAdminSession, (c) => {
20256
20367
  const pluginParam = c.req.query("plugin");
20257
20368
  if (pluginParam) {
20258
20369
  if (SLUG_UNSAFE(pluginParam)) return c.json({ error: "Invalid plugin" }, 400);
20259
- const file = resolve25(account.accountDir, "plugins", pluginParam, "agents", `${slug}.md`);
20260
- if (!existsSync34(file)) return c.json({ error: "Agent not found" }, 404);
20370
+ const file = resolve26(account.accountDir, "plugins", pluginParam, "agents", `${slug}.md`);
20371
+ if (!existsSync33(file)) return c.json({ error: "Agent not found" }, 404);
20261
20372
  const { fm, body } = splitFrontmatter(readFileSync35(file, "utf-8"));
20262
- const skills = subdirs(resolve25(account.accountDir, "plugins", pluginParam, "skills"));
20373
+ const skills = subdirs(resolve26(account.accountDir, "plugins", pluginParam, "skills"));
20263
20374
  const { riskByTool, failed: failed2 } = loadRiskSurface();
20264
20375
  const tools = parseToolsLine(fm.tools);
20265
20376
  const r = classify(tools, riskByTool, failed2 !== null);
@@ -20286,9 +20397,9 @@ app23.get("/:slug", requireAdminSession, (c) => {
20286
20397
  }
20287
20398
  });
20288
20399
  }
20289
- const agentDir = resolve25(account.accountDir, "agents", slug);
20290
- const configPath3 = resolve25(agentDir, "config.json");
20291
- if (!existsSync34(configPath3)) return c.json({ error: "Agent not found" }, 404);
20400
+ const agentDir = resolve26(account.accountDir, "agents", slug);
20401
+ const configPath3 = resolve26(agentDir, "config.json");
20402
+ if (!existsSync33(configPath3)) return c.json({ error: "Agent not found" }, 404);
20292
20403
  let config;
20293
20404
  try {
20294
20405
  config = JSON.parse(readFileSync35(configPath3, "utf-8"));
@@ -20299,9 +20410,9 @@ app23.get("/:slug", requireAdminSession, (c) => {
20299
20410
  const docs = {};
20300
20411
  const present = {};
20301
20412
  for (const { file, key: key2 } of OWNED_DOCS) {
20302
- const p = resolve25(agentDir, file);
20413
+ const p = resolve26(agentDir, file);
20303
20414
  try {
20304
- if (existsSync34(p)) {
20415
+ if (existsSync33(p)) {
20305
20416
  docs[key2] = readFileSync35(p, "utf-8");
20306
20417
  present[key2] = true;
20307
20418
  } else {
@@ -20350,7 +20461,7 @@ async function resolveWritableAccount(c, op, slug) {
20350
20461
  return account;
20351
20462
  }
20352
20463
  function reconcileRoutingProse(accountDir, accountId, op, slug) {
20353
- const script = resolve25(PLATFORM_ROOT, "scripts", "lib", "agents-md-bootstrap.sh");
20464
+ const script = resolve26(PLATFORM_ROOT, "scripts", "lib", "agents-md-bootstrap.sh");
20354
20465
  try {
20355
20466
  execFileSync6("bash", [script, accountDir], { encoding: "utf-8", timeout: 1e4 });
20356
20467
  return true;
@@ -20361,8 +20472,8 @@ function reconcileRoutingProse(accountDir, accountId, op, slug) {
20361
20472
  }
20362
20473
  }
20363
20474
  function reconcileAuthoredRoutingLine(accountDir, accountId, op, source) {
20364
- const agentsMd = resolve25(accountDir, "agents", "admin", "AGENTS.md");
20365
- if (!existsSync34(agentsMd) || !existsSync34(source)) return false;
20475
+ const agentsMd = resolve26(accountDir, "agents", "admin", "AGENTS.md");
20476
+ if (!existsSync33(agentsMd) || !existsSync33(source)) return false;
20366
20477
  try {
20367
20478
  const { fm } = splitFrontmatter(readFileSync35(source, "utf-8"));
20368
20479
  if (!fm.name) return false;
@@ -20385,18 +20496,18 @@ app23.post("/:slug/disable", async (c) => {
20385
20496
  const resolved = await resolveWritableAccount(c, "disable", slug);
20386
20497
  if ("status" in resolved) return c.json({ error: resolved.error }, resolved.status);
20387
20498
  const filename = `${slug}.md`;
20388
- const live = resolve25(resolved.accountDir, ...SPECIALISTS_DIR, filename);
20389
- const quarantined = resolve25(resolved.accountDir, ...QUARANTINE_DIR, filename);
20390
- if (!authoredAgentFilenames(resolved.accountDir).has(filename)) {
20499
+ const live = resolve26(resolved.accountDir, ...SPECIALISTS_DIR, filename);
20500
+ const quarantined = resolve26(resolved.accountDir, ...QUARANTINE_DIR, filename);
20501
+ if (!authoredAgentFiles(resolved.accountDir).has(filename)) {
20391
20502
  console.error(`[admin/agents] op=disable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} origin=not-authored eligible=false outcome=refused reason=not-user-created`);
20392
20503
  return c.json({ error: "Only an agent you created can be switched off here. A shipped agent is stopped by suspending the routines that dispatch it." }, 404);
20393
20504
  }
20394
- if (!existsSync34(live)) {
20505
+ if (!existsSync33(live)) {
20395
20506
  console.error(`[admin/agents] op=disable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} origin=specialist eligible=true outcome=refused reason=not-dispatchable`);
20396
20507
  return c.json({ error: "This agent is not in the dispatchable set, so there is nothing to switch off." }, 409);
20397
20508
  }
20398
20509
  try {
20399
- mkdirSync7(resolve25(resolved.accountDir, ...QUARANTINE_DIR), { recursive: true });
20510
+ mkdirSync7(resolve26(resolved.accountDir, ...QUARANTINE_DIR), { recursive: true });
20400
20511
  renameSync10(live, quarantined);
20401
20512
  } catch (err) {
20402
20513
  const msg = err instanceof Error ? err.message : String(err);
@@ -20413,28 +20524,28 @@ app23.post("/:slug/enable", async (c) => {
20413
20524
  const resolved = await resolveWritableAccount(c, "enable", slug);
20414
20525
  if ("status" in resolved) return c.json({ error: resolved.error }, resolved.status);
20415
20526
  const filename = `${slug}.md`;
20416
- const live = resolve25(resolved.accountDir, ...SPECIALISTS_DIR, filename);
20417
- const quarantined = resolve25(resolved.accountDir, ...QUARANTINE_DIR, filename);
20418
- const authored = authoredAgentFilenames(resolved.accountDir).has(filename);
20527
+ const live = resolve26(resolved.accountDir, ...SPECIALISTS_DIR, filename);
20528
+ const quarantined = resolve26(resolved.accountDir, ...QUARANTINE_DIR, filename);
20529
+ const authored = authoredAgentFiles(resolved.accountDir).has(filename);
20419
20530
  const origin = authored ? "specialist" : "shipped";
20420
20531
  let restored = false;
20421
20532
  try {
20422
20533
  if (authored) {
20423
- if (!existsSync34(quarantined)) {
20534
+ if (!existsSync33(quarantined)) {
20424
20535
  console.error(`[admin/agents] op=enable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} origin=specialist eligible=true outcome=refused reason=not-disabled`);
20425
20536
  return c.json({ error: "This agent is not disabled" }, 404);
20426
20537
  }
20427
- mkdirSync7(resolve25(resolved.accountDir, ...SPECIALISTS_DIR), { recursive: true });
20538
+ mkdirSync7(resolve26(resolved.accountDir, ...SPECIALISTS_DIR), { recursive: true });
20428
20539
  renameSync10(quarantined, live);
20429
20540
  restored = true;
20430
20541
  } else {
20431
20542
  const disabled = readDisabledStoreStrict(resolved.accountDir);
20432
- if (!existsSync34(quarantined) && !disabled.has(filename)) {
20543
+ if (!existsSync33(quarantined) && !disabled.has(filename)) {
20433
20544
  console.error(`[admin/agents] op=enable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} origin=shipped eligible=true outcome=refused reason=not-disabled`);
20434
20545
  return c.json({ error: "This agent is not disabled" }, 404);
20435
20546
  }
20436
- if (existsSync34(quarantined)) {
20437
- mkdirSync7(resolve25(resolved.accountDir, ...SPECIALISTS_DIR), { recursive: true });
20547
+ if (existsSync33(quarantined)) {
20548
+ mkdirSync7(resolve26(resolved.accountDir, ...SPECIALISTS_DIR), { recursive: true });
20438
20549
  renameSync10(quarantined, live);
20439
20550
  restored = true;
20440
20551
  }
@@ -20475,8 +20586,8 @@ app23.delete("/:slug", async (c) => {
20475
20586
  console.error(`[admin/agents] op=delete rejected reason=${authz.reason} accountId=${account.accountId.slice(0, 8)} slug=${slug}`);
20476
20587
  return c.json({ error: "Not authorized for this account" }, 403);
20477
20588
  }
20478
- const agentDir = resolve25(account.accountDir, "agents", slug);
20479
- if (!existsSync34(agentDir)) {
20589
+ const agentDir = resolve26(account.accountDir, "agents", slug);
20590
+ if (!existsSync33(agentDir)) {
20480
20591
  return c.json({ error: "Agent not found" }, 404);
20481
20592
  }
20482
20593
  try {
@@ -20518,8 +20629,8 @@ app23.post("/:slug/project", async (c) => {
20518
20629
  console.error(`[admin/agents] op=project rejected reason=${authz.reason} accountId=${account.accountId.slice(0, 8)} slug=${slug}`);
20519
20630
  return c.json({ error: "Not authorized for this account" }, 403);
20520
20631
  }
20521
- const agentDir = resolve25(account.accountDir, "agents", slug);
20522
- if (!existsSync34(agentDir)) {
20632
+ const agentDir = resolve26(account.accountDir, "agents", slug);
20633
+ if (!existsSync33(agentDir)) {
20523
20634
  console.error(`[admin/agents] op=project rejected reason=agent-not-on-disk accountId=${account.accountId.slice(0, 8)} slug=${slug} probed=${agentDir}`);
20524
20635
  return c.json({ error: "Agent not found on disk" }, 404);
20525
20636
  }
@@ -20543,7 +20654,7 @@ init_account();
20543
20654
  init_neo4j_store();
20544
20655
  import crypto2 from "crypto";
20545
20656
  import { resolve as resolvePath } from "path";
20546
- import { existsSync as existsSync35, mkdirSync as mkdirSync8, createWriteStream } from "fs";
20657
+ import { existsSync as existsSync34, mkdirSync as mkdirSync8, createWriteStream } from "fs";
20547
20658
 
20548
20659
  // app/lib/admin-conversation-cascade.ts
20549
20660
  init_neo4j_store();
@@ -20711,7 +20822,7 @@ function validateAndShapeAttachments(raws, conversationAccountId, sessionId, mes
20711
20822
  let reason = null;
20712
20823
  if (!a.attachmentId || !a.filename || !a.mimeType || !a.storagePath) reason = "schema-fail";
20713
20824
  else if (a.accountId !== conversationAccountId) reason = "account-mismatch";
20714
- else if (!existsSync35(a.storagePath)) reason = "missing-file";
20825
+ else if (!existsSync34(a.storagePath)) reason = "missing-file";
20715
20826
  if (reason) {
20716
20827
  invalid++;
20717
20828
  try {
@@ -20827,7 +20938,7 @@ app24.get("/", requireAdminSession, async (c) => {
20827
20938
  if (!userId) return c.json({ error: "User identity required \u2014 authenticate with users.json PIN" }, 401);
20828
20939
  try {
20829
20940
  const flushed = await listAdminSessions(accountId, userId, 20);
20830
- const sessions2 = flushed.map((r) => ({
20941
+ const sessions3 = flushed.map((r) => ({
20831
20942
  sessionId: r.sessionId,
20832
20943
  cacheKey: null,
20833
20944
  name: r.name,
@@ -20835,15 +20946,15 @@ app24.get("/", requireAdminSession, async (c) => {
20835
20946
  phase: "flushed",
20836
20947
  channel: r.channel
20837
20948
  })).sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).slice(0, 20);
20838
- const channelCounts = sessions2.reduce((acc, s) => {
20949
+ const channelCounts = sessions3.reduce((acc, s) => {
20839
20950
  const k = s.channel ?? "unknown";
20840
20951
  acc[k] = (acc[k] ?? 0) + 1;
20841
20952
  return acc;
20842
20953
  }, {});
20843
20954
  console.error(
20844
- `[conversations-list] render rows=${sessions2.length} channels=${JSON.stringify(channelCounts)}`
20955
+ `[conversations-list] render rows=${sessions3.length} channels=${JSON.stringify(channelCounts)}`
20845
20956
  );
20846
- return c.json({ sessions: sessions2 });
20957
+ return c.json({ sessions: sessions3 });
20847
20958
  } catch (err) {
20848
20959
  console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
20849
20960
  return c.json({ error: "Failed to fetch sessions" }, 500);
@@ -21244,8 +21355,8 @@ init_claude_agent();
21244
21355
  // app/lib/claude-agent/spawn-context.ts
21245
21356
  init_account();
21246
21357
  init_neo4j_store();
21247
- import { existsSync as existsSync36, readFileSync as readFileSync36, readdirSync as readdirSync23, statSync as statSync23 } from "fs";
21248
- import { dirname as dirname14, resolve as resolve26, join as join41 } from "path";
21358
+ import { existsSync as existsSync35, readFileSync as readFileSync36, readdirSync as readdirSync23, statSync as statSync23 } from "fs";
21359
+ import { dirname as dirname14, resolve as resolve27, join as join41 } from "path";
21249
21360
  async function resolveOwnerProfileBlock(accountId, userId) {
21250
21361
  if (!userId) return { ok: false, reason: "missing-user-id" };
21251
21362
  try {
@@ -21291,7 +21402,7 @@ function extractPluginToolDescriptions(pluginDir) {
21291
21402
  ];
21292
21403
  let raw = null;
21293
21404
  for (const path of candidates) {
21294
- if (!existsSync36(path)) continue;
21405
+ if (!existsSync35(path)) continue;
21295
21406
  try {
21296
21407
  raw = readFileSync36(path, "utf-8");
21297
21408
  break;
@@ -21342,8 +21453,8 @@ function parseSkillPathsFromFrontmatter(fm) {
21342
21453
  }
21343
21454
  function listPluginDirs(accountPluginsDir) {
21344
21455
  const out = /* @__PURE__ */ new Map();
21345
- const platformPlugins = resolve26(PLATFORM_ROOT, "plugins");
21346
- if (existsSync36(platformPlugins)) {
21456
+ const platformPlugins = resolve27(PLATFORM_ROOT, "plugins");
21457
+ if (existsSync35(platformPlugins)) {
21347
21458
  for (const name of readdirSync23(platformPlugins)) {
21348
21459
  const dir = join41(platformPlugins, name);
21349
21460
  try {
@@ -21352,11 +21463,11 @@ function listPluginDirs(accountPluginsDir) {
21352
21463
  }
21353
21464
  }
21354
21465
  }
21355
- const premiumRoot = resolve26(dirname14(PLATFORM_ROOT), "premium-plugins");
21356
- if (existsSync36(premiumRoot)) {
21466
+ const premiumRoot = resolve27(dirname14(PLATFORM_ROOT), "premium-plugins");
21467
+ if (existsSync35(premiumRoot)) {
21357
21468
  for (const bundle of readdirSync23(premiumRoot)) {
21358
21469
  const bundlePlugins = join41(premiumRoot, bundle, "plugins");
21359
- if (!existsSync36(bundlePlugins)) continue;
21470
+ if (!existsSync35(bundlePlugins)) continue;
21360
21471
  try {
21361
21472
  for (const name of readdirSync23(bundlePlugins)) {
21362
21473
  const dir = join41(bundlePlugins, name);
@@ -21369,7 +21480,7 @@ function listPluginDirs(accountPluginsDir) {
21369
21480
  }
21370
21481
  }
21371
21482
  }
21372
- if (accountPluginsDir && existsSync36(accountPluginsDir)) {
21483
+ if (accountPluginsDir && existsSync35(accountPluginsDir)) {
21373
21484
  try {
21374
21485
  for (const name of readdirSync23(accountPluginsDir)) {
21375
21486
  if (out.has(name)) continue;
@@ -21385,10 +21496,10 @@ function listPluginDirs(accountPluginsDir) {
21385
21496
  return out;
21386
21497
  }
21387
21498
  function accountPluginsDirFor(accountId) {
21388
- return resolve26(PLATFORM_ROOT, "..", "data/accounts", accountId, "plugins");
21499
+ return resolve27(PLATFORM_ROOT, "..", "data/accounts", accountId, "plugins");
21389
21500
  }
21390
21501
  function listAccountPluginNames(accountPluginsDir) {
21391
- if (!existsSync36(accountPluginsDir)) return [];
21502
+ if (!existsSync35(accountPluginsDir)) return [];
21392
21503
  try {
21393
21504
  return readdirSync23(accountPluginsDir).filter((name) => {
21394
21505
  try {
@@ -21402,8 +21513,8 @@ function listAccountPluginNames(accountPluginsDir) {
21402
21513
  }
21403
21514
  }
21404
21515
  function readEnabledPlugins(accountId) {
21405
- const accountFile = resolve26(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
21406
- if (!existsSync36(accountFile)) return /* @__PURE__ */ new Set();
21516
+ const accountFile = resolve27(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
21517
+ if (!existsSync35(accountFile)) return /* @__PURE__ */ new Set();
21407
21518
  try {
21408
21519
  const parsed = JSON.parse(readFileSync36(accountFile, "utf-8"));
21409
21520
  return new Set(
@@ -21414,7 +21525,7 @@ function readEnabledPlugins(accountId) {
21414
21525
  }
21415
21526
  }
21416
21527
  function readFrontmatter(path) {
21417
- if (!existsSync36(path)) return null;
21528
+ if (!existsSync35(path)) return null;
21418
21529
  let raw;
21419
21530
  try {
21420
21531
  raw = readFileSync36(path, "utf-8");
@@ -21452,7 +21563,7 @@ ${fm}
21452
21563
  `plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
21453
21564
  );
21454
21565
  if (toolNames.length > 0 && descMap.size === 0) {
21455
- const hasSource = existsSync36(join41(dir, "mcp/dist/index.js")) || existsSync36(join41(dir, "mcp/src/index.ts"));
21566
+ const hasSource = existsSync35(join41(dir, "mcp/dist/index.js")) || existsSync35(join41(dir, "mcp/src/index.ts"));
21456
21567
  if (hasSource) {
21457
21568
  console.warn(
21458
21569
  `[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
@@ -21478,8 +21589,8 @@ ${skillFm}
21478
21589
  return out;
21479
21590
  }
21480
21591
  function computeSpecialistDomains(accountId) {
21481
- const specialistsDir = resolve26(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
21482
- if (!existsSync36(specialistsDir)) return [];
21592
+ const specialistsDir = resolve27(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
21593
+ if (!existsSync35(specialistsDir)) return [];
21483
21594
  let entries2;
21484
21595
  try {
21485
21596
  entries2 = readdirSync23(specialistsDir);
@@ -21524,8 +21635,8 @@ ${fm}
21524
21635
  return out;
21525
21636
  }
21526
21637
  function computeDormantPlugins(accountId) {
21527
- const accountFile = resolve26(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
21528
- if (!existsSync36(accountFile)) return [];
21638
+ const accountFile = resolve27(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
21639
+ if (!existsSync35(accountFile)) return [];
21529
21640
  let enabled;
21530
21641
  try {
21531
21642
  const raw = readFileSync36(accountFile, "utf-8");
@@ -21539,8 +21650,8 @@ function computeDormantPlugins(accountId) {
21539
21650
  );
21540
21651
  return [];
21541
21652
  }
21542
- const pluginsDir = resolve26(PLATFORM_ROOT, "plugins");
21543
- if (!existsSync36(pluginsDir)) return [];
21653
+ const pluginsDir = resolve27(PLATFORM_ROOT, "plugins");
21654
+ if (!existsSync35(pluginsDir)) return [];
21544
21655
  let entries2;
21545
21656
  try {
21546
21657
  entries2 = readdirSync23(pluginsDir);
@@ -21550,8 +21661,8 @@ function computeDormantPlugins(accountId) {
21550
21661
  const out = [];
21551
21662
  for (const name of entries2) {
21552
21663
  if (enabled.has(name)) continue;
21553
- const manifestPath = resolve26(pluginsDir, name, "PLUGIN.md");
21554
- if (!existsSync36(manifestPath)) continue;
21664
+ const manifestPath = resolve27(pluginsDir, name, "PLUGIN.md");
21665
+ if (!existsSync35(manifestPath)) continue;
21555
21666
  let manifest;
21556
21667
  try {
21557
21668
  manifest = readFileSync36(manifestPath, "utf-8");
@@ -21569,11 +21680,11 @@ function computeDormantPlugins(accountId) {
21569
21680
 
21570
21681
  // server/routes/admin/claude-sessions.ts
21571
21682
  init_neo4j_store();
21572
- import { existsSync as existsSync37, readFileSync as readFileSync37 } from "fs";
21573
- import { resolve as resolve27 } from "path";
21683
+ import { existsSync as existsSync36, readFileSync as readFileSync37 } from "fs";
21684
+ import { resolve as resolve28 } from "path";
21574
21685
  function readTunnelState(brandConfigDir) {
21575
21686
  const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
21576
- if (!existsSync37(statePath)) return null;
21687
+ if (!existsSync36(statePath)) return null;
21577
21688
  try {
21578
21689
  const parsed = JSON.parse(readFileSync37(statePath, "utf-8"));
21579
21690
  const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
@@ -21586,10 +21697,10 @@ function readTunnelState(brandConfigDir) {
21586
21697
  }
21587
21698
  }
21588
21699
  function resolveTunnelUrl() {
21589
- const platformRoot5 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve27(process.cwd(), "..");
21700
+ const platformRoot5 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve28(process.cwd(), "..");
21590
21701
  let brand;
21591
21702
  try {
21592
- brand = JSON.parse(readFileSync37(resolve27(platformRoot5, "config", "brand.json"), "utf-8"));
21703
+ brand = JSON.parse(readFileSync37(resolve28(platformRoot5, "config", "brand.json"), "utf-8"));
21593
21704
  } catch {
21594
21705
  return null;
21595
21706
  }
@@ -22049,11 +22160,11 @@ init_helpers();
22049
22160
  init_claude_agent();
22050
22161
  init_data_path();
22051
22162
  init_sidebar_sessions();
22052
- import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync38 } from "fs";
22163
+ import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync37 } from "fs";
22053
22164
  import { readdir as readdir4, readFile as readFile9, stat as stat6, mkdir as mkdir3, unlink as unlink3, rename as rename3, rmdir } from "fs/promises";
22054
22165
  import { realpathSync as realpathSync7 } from "fs";
22055
22166
  import { randomUUID as randomUUID7 } from "crypto";
22056
- import { basename as basename13, dirname as dirname15, join as join44, relative as relative6, resolve as resolve29, sep as sep9 } from "path";
22167
+ import { basename as basename13, dirname as dirname15, join as join44, relative as relative6, resolve as resolve30, sep as sep9 } from "path";
22057
22168
  import { Readable as Readable2 } from "stream";
22058
22169
  init_attachments();
22059
22170
 
@@ -22377,7 +22488,7 @@ async function cascadeDeleteDocument(params) {
22377
22488
  init_data_path();
22378
22489
  init_neo4j_store();
22379
22490
  import * as fsp from "fs/promises";
22380
- import { resolve as resolve28, relative as relative5, join as join43, basename as basename12, extname, sep as sep8 } from "path";
22491
+ import { resolve as resolve29, relative as relative5, join as join43, basename as basename12, extname, sep as sep8 } from "path";
22381
22492
  import { tmpdir as tmpdir3 } from "os";
22382
22493
  import { execFile } from "child_process";
22383
22494
  import { promisify } from "util";
@@ -22539,14 +22650,14 @@ async function walkSubtree(root, dataRoot, out) {
22539
22650
  return { clean: false };
22540
22651
  }
22541
22652
  let clean = true;
22542
- for (const entry of entries2) {
22543
- if (entry.isSymbolicLink()) continue;
22544
- if (entry.isDirectory() && entry.name === ".uploads-tmp") continue;
22545
- const abs = join43(root, entry.name);
22546
- if (entry.isDirectory()) {
22653
+ for (const entry2 of entries2) {
22654
+ if (entry2.isSymbolicLink()) continue;
22655
+ if (entry2.isDirectory() && entry2.name === ".uploads-tmp") continue;
22656
+ const abs = join43(root, entry2.name);
22657
+ if (entry2.isDirectory()) {
22547
22658
  const sub = await walkSubtree(abs, dataRoot, out);
22548
22659
  if (!sub.clean) clean = false;
22549
- } else if (entry.isFile()) {
22660
+ } else if (entry2.isFile()) {
22550
22661
  try {
22551
22662
  const st = await fsp.stat(abs);
22552
22663
  out.push({
@@ -22697,7 +22808,7 @@ async function reconcileFileIndex(accountId, opts) {
22697
22808
  return withSession2(opts, async (session) => {
22698
22809
  const walked = [];
22699
22810
  const subtreeRoots = [
22700
- resolve28(dataRoot, "accounts", accountId)
22811
+ resolve29(dataRoot, "accounts", accountId)
22701
22812
  ];
22702
22813
  let clean = true;
22703
22814
  for (const root of subtreeRoots) {
@@ -22780,7 +22891,7 @@ async function reconcileFileIndexDebounced(accountId, opts) {
22780
22891
  async function indexFile(accountId, relativePath, opts) {
22781
22892
  const dataRoot = opts?.dataRoot ?? DATA_ROOT;
22782
22893
  const embed2 = opts?.embed ?? embed;
22783
- const absolute = resolve28(dataRoot, relativePath);
22894
+ const absolute = resolve29(dataRoot, relativePath);
22784
22895
  await withSession2(opts, async (session) => {
22785
22896
  const st = await fsp.stat(absolute);
22786
22897
  const wf = {
@@ -22838,14 +22949,14 @@ function buildZip(entries2) {
22838
22949
  const localChunks = [];
22839
22950
  const centralChunks = [];
22840
22951
  let offset = 0;
22841
- for (const entry of entries2) {
22842
- const nameBytes = Buffer.from(entry.name, "utf8");
22843
- const crc = crc32(entry.data);
22844
- const uncompressedSize = entry.data.length;
22845
- const deflated = deflateRawSync(entry.data);
22952
+ for (const entry2 of entries2) {
22953
+ const nameBytes = Buffer.from(entry2.name, "utf8");
22954
+ const crc = crc32(entry2.data);
22955
+ const uncompressedSize = entry2.data.length;
22956
+ const deflated = deflateRawSync(entry2.data);
22846
22957
  const useDeflate = deflated.length < uncompressedSize;
22847
22958
  const method = useDeflate ? 8 : 0;
22848
- const payload = useDeflate ? deflated : entry.data;
22959
+ const payload = useDeflate ? deflated : entry2.data;
22849
22960
  const compressedSize = payload.length;
22850
22961
  const localHeader = Buffer.alloc(30);
22851
22962
  localHeader.writeUInt32LE(LOCAL_SIG, 0);
@@ -22929,7 +23040,7 @@ async function readMeta2(absDir, baseName) {
22929
23040
  }
22930
23041
  async function readAccountNames() {
22931
23042
  const map = /* @__PURE__ */ new Map();
22932
- const accountsDir = resolve29(DATA_ROOT, "accounts");
23043
+ const accountsDir = resolve30(DATA_ROOT, "accounts");
22933
23044
  let names;
22934
23045
  try {
22935
23046
  names = await readdir4(accountsDir);
@@ -22938,7 +23049,7 @@ async function readAccountNames() {
22938
23049
  }
22939
23050
  for (const name of names) {
22940
23051
  if (!UUID_RE3.test(name)) continue;
22941
- const configPath3 = resolve29(accountsDir, name, "account.json");
23052
+ const configPath3 = resolve30(accountsDir, name, "account.json");
22942
23053
  try {
22943
23054
  const raw = await readFile9(configPath3, "utf8");
22944
23055
  const parsed = JSON.parse(raw);
@@ -22971,47 +23082,47 @@ async function servableFilesIn(dirAbs, uuid) {
22971
23082
  }
22972
23083
  return out;
22973
23084
  }
22974
- async function enrich(absolute, entry, accountNames) {
22975
- if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
22976
- const dirAbs = join44(absolute, entry.name);
22977
- const meta = await readMeta2(dirAbs, entry.name);
23085
+ async function enrich(absolute, entry2, accountNames) {
23086
+ if (entry2.kind === "directory" && UUID_RE3.test(entry2.name)) {
23087
+ const dirAbs = join44(absolute, entry2.name);
23088
+ const meta = await readMeta2(dirAbs, entry2.name);
22978
23089
  if (meta?.filename) {
22979
- const servable = await servableFilesIn(dirAbs, entry.name);
23090
+ const servable = await servableFilesIn(dirAbs, entry2.name);
22980
23091
  if (servable.length === 1) {
22981
23092
  const innerName = servable[0];
22982
23093
  let size = null;
22983
- let modifiedAt = entry.modifiedAt;
23094
+ let modifiedAt = entry2.modifiedAt;
22984
23095
  try {
22985
23096
  const st = await stat6(join44(dirAbs, innerName));
22986
23097
  size = st.size;
22987
23098
  modifiedAt = st.mtime.toISOString();
22988
23099
  } catch {
22989
23100
  }
22990
- entry.kind = "file";
22991
- entry.displayName = meta.filename;
22992
- entry.mimeType = meta.mimeType;
22993
- entry.sizeBytes = size;
22994
- entry.modifiedAt = modifiedAt;
22995
- entry.entryPath = `${entry.name}/${innerName}`;
23101
+ entry2.kind = "file";
23102
+ entry2.displayName = meta.filename;
23103
+ entry2.mimeType = meta.mimeType;
23104
+ entry2.sizeBytes = size;
23105
+ entry2.modifiedAt = modifiedAt;
23106
+ entry2.entryPath = `${entry2.name}/${innerName}`;
22996
23107
  return "flattened";
22997
23108
  }
22998
- entry.displayName = meta.filename;
23109
+ entry2.displayName = meta.filename;
22999
23110
  return "kept-as-dir";
23000
23111
  }
23001
- const accountName = accountNames.get(entry.name);
23112
+ const accountName = accountNames.get(entry2.name);
23002
23113
  if (accountName) {
23003
- entry.displayName = accountName;
23114
+ entry2.displayName = accountName;
23004
23115
  }
23005
23116
  return null;
23006
23117
  }
23007
- if (entry.kind === "file") {
23008
- const dot = entry.name.lastIndexOf(".");
23009
- const base = dot === -1 ? entry.name : entry.name.slice(0, dot);
23118
+ if (entry2.kind === "file") {
23119
+ const dot = entry2.name.lastIndexOf(".");
23120
+ const base = dot === -1 ? entry2.name : entry2.name.slice(0, dot);
23010
23121
  if (UUID_RE3.test(base)) {
23011
23122
  const meta = await readMeta2(absolute, base);
23012
23123
  if (meta?.filename) {
23013
- entry.displayName = meta.filename;
23014
- entry.mimeType = meta.mimeType;
23124
+ entry2.displayName = meta.filename;
23125
+ entry2.mimeType = meta.mimeType;
23015
23126
  }
23016
23127
  }
23017
23128
  }
@@ -23102,6 +23213,10 @@ function isValidEntryName(name) {
23102
23213
  if (name.startsWith("@")) return false;
23103
23214
  return !/[\\/\0]/.test(name);
23104
23215
  }
23216
+ function crossAccountOwner(relPath) {
23217
+ const segs = relPath.split("/").filter(Boolean);
23218
+ return segs.length >= 2 && segs[0] === "accounts" ? segs[1] : "";
23219
+ }
23105
23220
  function resolveOwnAccountWrite(rawPath, accountId, endpoint) {
23106
23221
  const res = resolveDataPath(rawPath);
23107
23222
  if (!res.ok) {
@@ -23169,8 +23284,7 @@ app29.get("/", requireAdminSession, async (c) => {
23169
23284
  }
23170
23285
  const { absolute, relative: relPath } = resolution;
23171
23286
  if (crossesForeignAccountPartition(relPath, accountId)) {
23172
- console.error(`[data] account-scope-blocked endpoint="/api/admin/files" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
23173
- return c.json({ error: "Not found" }, 404);
23287
+ console.error(`[data] op=cross-account-read endpoint="GET /api/admin/files" path="${relPath}" pinned=${accountId.slice(0, 8)}\u2026 owner=${crossAccountOwner(relPath).slice(0, 8)}\u2026`);
23174
23288
  }
23175
23289
  try {
23176
23290
  const info = await stat6(absolute);
@@ -23346,8 +23460,7 @@ app29.get("/download", requireAdminSession, async (c) => {
23346
23460
  return c.json({ error: "Not found" }, 404);
23347
23461
  }
23348
23462
  } else if (root === "data" && crossesForeignAccountPartition(relPath, accountId)) {
23349
- console.error(`[data] account-scope-blocked endpoint="/api/admin/files/download" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
23350
- return c.json({ error: "Not found" }, 404);
23463
+ console.error(`[data] op=cross-account-read endpoint="GET /api/admin/files/download" path="${relPath}" pinned=${accountId.slice(0, 8)}\u2026 owner=${crossAccountOwner(relPath).slice(0, 8)}\u2026`);
23351
23464
  }
23352
23465
  try {
23353
23466
  const info = await stat6(absolute);
@@ -23460,8 +23573,7 @@ app29.get("/download-zip", requireAdminSession, async (c) => {
23460
23573
  }
23461
23574
  const { absolute, relative: relPath } = resolution;
23462
23575
  if (crossesForeignAccountPartition(relPath, accountId)) {
23463
- console.error(`[data] account-scope-blocked endpoint="/api/admin/files/download-zip" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
23464
- return c.json({ error: "Not found" }, 404);
23576
+ console.error(`[data] op=cross-account-read endpoint="GET /api/admin/files/download-zip" path="${relPath}" pinned=${accountId.slice(0, 8)}\u2026 owner=${crossAccountOwner(relPath).slice(0, 8)}\u2026`);
23465
23577
  }
23466
23578
  try {
23467
23579
  const info = await stat6(absolute);
@@ -23469,9 +23581,6 @@ app29.get("/download-zip", requireAdminSession, async (c) => {
23469
23581
  dirsWalked++;
23470
23582
  const parentAbs = dirname15(absolute);
23471
23583
  for await (const fileAbs of walkRegularFiles(absolute)) {
23472
- const within = relative6(absolute, fileAbs).split(sep9).join("/");
23473
- const fileRel = relPath === "" || relPath === "." ? within : `${relPath}/${within}`;
23474
- if (crossesForeignAccountPartition(fileRel, accountId)) continue;
23475
23584
  const fileInfo = await stat6(fileAbs);
23476
23585
  const member = relative6(parentAbs, fileAbs).split(sep9).join("/");
23477
23586
  const over = await addFile(fileAbs, fileInfo.size, member);
@@ -23542,8 +23651,8 @@ async function resolveUploadTarget(c, accountId, uid) {
23542
23651
  return { ok: false, status: 403, error: "Path is outside your account folder" };
23543
23652
  }
23544
23653
  }
23545
- const destDir = relDir === "." ? base.absolute : resolve29(base.absolute, relDir);
23546
- const destPath = resolve29(destDir, finalName);
23654
+ const destDir = relDir === "." ? base.absolute : resolve30(base.absolute, relDir);
23655
+ const destPath = resolve30(destDir, finalName);
23547
23656
  if (relpath) {
23548
23657
  const firstCreated = await mkdir3(destDir, { recursive: true });
23549
23658
  if (firstCreated) console.error(`[data-upload] op=mkdir uid=${uid} token=${token} dir="${destRel}"`);
@@ -23560,7 +23669,7 @@ async function resolveUploadTarget(c, accountId, uid) {
23560
23669
  return { ok: true, destDir, destPath, destRel, finalName, mimeType, relpath, token };
23561
23670
  }
23562
23671
  async function logUploadsTmpCensus(accountId) {
23563
- const tmpDir = resolve29(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
23672
+ const tmpDir = resolve30(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
23564
23673
  let live = 0;
23565
23674
  try {
23566
23675
  live = (await readdir4(tmpDir)).length;
@@ -23587,9 +23696,9 @@ app29.post("/upload", requireAdminSession, async (c) => {
23587
23696
  if (!body) return c.json({ error: "Empty request body" }, 400);
23588
23697
  console.error(`[data-upload] op=request uid=${uid} account=${accountId} dir="${destRel}" token=${token} rel="${relpath ?? ""}" declaredBytes=${declaredBytes}`);
23589
23698
  const ceiling = dataUploadCeiling();
23590
- const tmpDir = resolve29(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
23699
+ const tmpDir = resolve30(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
23591
23700
  await mkdir3(tmpDir, { recursive: true });
23592
- const tmpPath = resolve29(tmpDir, `${uid}.part`);
23701
+ const tmpPath = resolve30(tmpDir, `${uid}.part`);
23593
23702
  const out = createWriteStream2(tmpPath);
23594
23703
  const t0 = Date.now();
23595
23704
  let received2 = 0;
@@ -23607,7 +23716,7 @@ app29.post("/upload", requireAdminSession, async (c) => {
23607
23716
  });
23608
23717
  await unlink3(tmpPath).catch(() => {
23609
23718
  });
23610
- const tempRemoved = !existsSync38(tmpPath);
23719
+ const tempRemoved = !existsSync37(tmpPath);
23611
23720
  console.error(`[data-upload] op=cleanup uid=${uid} tempRemoved=${tempRemoved}`);
23612
23721
  };
23613
23722
  const reader = body.getReader();
@@ -23681,7 +23790,7 @@ async function discardChunkUpload(uploadId, state) {
23681
23790
  });
23682
23791
  await unlink3(state.tmpPath).catch(() => {
23683
23792
  });
23684
- const tempRemoved = !existsSync38(state.tmpPath);
23793
+ const tempRemoved = !existsSync37(state.tmpPath);
23685
23794
  console.error(`[data-upload] op=cleanup uid=${uploadId} tempRemoved=${tempRemoved}`);
23686
23795
  }
23687
23796
  async function writeChunkBody(body, out, alreadyReceived, ceiling) {
@@ -23736,9 +23845,9 @@ async function handleChunkUpload(c, accountId) {
23736
23845
  }
23737
23846
  const target = await resolveUploadTarget(c, accountId, uploadId);
23738
23847
  if (!target.ok) return c.json({ error: target.error }, target.status);
23739
- const tmpDir = resolve29(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
23848
+ const tmpDir = resolve30(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
23740
23849
  await mkdir3(tmpDir, { recursive: true });
23741
- const tmpPath = resolve29(tmpDir, `${uploadId}.part`);
23850
+ const tmpPath = resolve30(tmpDir, `${uploadId}.part`);
23742
23851
  const out = createWriteStream2(tmpPath);
23743
23852
  const state2 = {
23744
23853
  accountId,
@@ -23844,7 +23953,7 @@ async function evictStaleChunkUploads(ttlMs) {
23844
23953
  return evicted;
23845
23954
  }
23846
23955
  async function sweepStaleUploadTemps() {
23847
- const accountsRoot = resolve29(DATA_ROOT, "accounts");
23956
+ const accountsRoot = resolve30(DATA_ROOT, "accounts");
23848
23957
  let accounts;
23849
23958
  try {
23850
23959
  accounts = await readdir4(accountsRoot, { withFileTypes: true });
@@ -23854,7 +23963,7 @@ async function sweepStaleUploadTemps() {
23854
23963
  let removed = 0;
23855
23964
  for (const acc of accounts) {
23856
23965
  if (!acc.isDirectory()) continue;
23857
- const tmpDir = resolve29(accountsRoot, acc.name, UPLOAD_TMP_DIR);
23966
+ const tmpDir = resolve30(accountsRoot, acc.name, UPLOAD_TMP_DIR);
23858
23967
  let entries2;
23859
23968
  try {
23860
23969
  entries2 = await readdir4(tmpDir);
@@ -23863,7 +23972,7 @@ async function sweepStaleUploadTemps() {
23863
23972
  }
23864
23973
  for (const name of entries2) {
23865
23974
  try {
23866
- await unlink3(resolve29(tmpDir, name));
23975
+ await unlink3(resolve30(tmpDir, name));
23867
23976
  removed++;
23868
23977
  } catch {
23869
23978
  }
@@ -23978,7 +24087,7 @@ app29.post("/folder", requireAdminSession, async (c) => {
23978
24087
  if (!isValidEntryName(name)) return c.json({ error: "Invalid folder name" }, 400);
23979
24088
  const parent = resolveSharedOrOwnAccountWrite(rawPath, accountId, "POST /api/admin/files/folder");
23980
24089
  if (!parent.ok) return c.json({ error: parent.error }, parent.status);
23981
- const newAbs = resolve29(parent.absolute, name);
24090
+ const newAbs = resolve30(parent.absolute, name);
23982
24091
  const newRel = `${parent.relative}/${name}`;
23983
24092
  try {
23984
24093
  const info = await stat6(parent.absolute);
@@ -24017,7 +24126,7 @@ app29.post("/rename", requireAdminSession, async (c) => {
24017
24126
  console.error(`[data] file-rename blocked path="${src.relative}" reason="protected"`);
24018
24127
  return c.json({ error: "Protected entry \u2014 refusing to rename" }, 403);
24019
24128
  }
24020
- const destAbs = resolve29(dirname15(src.absolute), newName);
24129
+ const destAbs = resolve30(dirname15(src.absolute), newName);
24021
24130
  const newRel = `${dirname15(src.relative)}/${newName}`.replace(/^\.\//, "");
24022
24131
  if (isProtectedFromRename(newRel)) {
24023
24132
  console.error(`[data] file-rename blocked path="${newRel}" reason="protected-target"`);
@@ -25050,7 +25159,7 @@ function isHiddenByDefault(label) {
25050
25159
  // server/lib/top-level-labels.ts
25051
25160
  var import_dist8 = __toESM(require_dist6(), 1);
25052
25161
  import { readdirSync as readdirSync24, readFileSync as readFileSync38 } from "fs";
25053
- import { join as join45, resolve as resolve30 } from "path";
25162
+ import { join as join45, resolve as resolve31 } from "path";
25054
25163
  var STATIC_TOP_LEVEL_LABELS = Object.freeze(
25055
25164
  /* @__PURE__ */ new Set([
25056
25165
  // Base-schema infra (schema-base.md has no top-level section).
@@ -25156,7 +25265,7 @@ function resolveReferencesDir() {
25156
25265
  if (process.env.MAXY_PLATFORM_ROOT) {
25157
25266
  return join45(process.env.MAXY_PLATFORM_ROOT, "plugins", "memory", "references");
25158
25267
  }
25159
- return resolve30(import.meta.dirname, "../../../plugins/memory/references");
25268
+ return resolve31(import.meta.dirname, "../../../plugins/memory/references");
25160
25269
  }
25161
25270
  function parseTableTopLevelLabels(referencesDir) {
25162
25271
  const labels = /* @__PURE__ */ new Set();
@@ -26342,8 +26451,8 @@ init_claude_agent();
26342
26451
  init_account();
26343
26452
  init_route_cache();
26344
26453
  import { readdir as readdir5, stat as stat7 } from "fs/promises";
26345
- import { resolve as resolve31, relative as relative7, isAbsolute as isAbsolute4, sep as sep10, basename as basename14 } from "path";
26346
- import { existsSync as existsSync39 } from "fs";
26454
+ import { resolve as resolve32, relative as relative7, isAbsolute as isAbsolute4, sep as sep10, basename as basename14 } from "path";
26455
+ import { existsSync as existsSync38 } from "fs";
26347
26456
  init_neo4j_store();
26348
26457
  init_attachments();
26349
26458
  init_data_path();
@@ -26361,11 +26470,11 @@ app36.get("/", requireAdminSession, async (c) => {
26361
26470
  return c.json({ error: "Account not found for session" }, 401);
26362
26471
  }
26363
26472
  const start = Date.now();
26364
- ensureAccountWatchers(accountId, resolve31(ACCOUNTS_DIR, accountId));
26473
+ ensureAccountWatchers(accountId, resolve32(ACCOUNTS_DIR, accountId));
26365
26474
  const { value, state } = await getOrCompute("sidebar-artefacts", accountId, async () => {
26366
26475
  const accountFiles = await fetchAccountFileArtefacts(accountId);
26367
26476
  if (accountFiles === null) return null;
26368
- const accountDir = resolve31(ACCOUNTS_DIR, accountId);
26477
+ const accountDir = resolve32(ACCOUNTS_DIR, accountId);
26369
26478
  const agents = await fetchAgentTemplateRows(accountDir);
26370
26479
  const artefacts = [...accountFiles, ...agents].sort(
26371
26480
  (a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")
@@ -26422,8 +26531,8 @@ async function fetchAccountFileArtefacts(accountId) {
26422
26531
  async function fetchAgentTemplateRows(accountDir) {
26423
26532
  const rows = [];
26424
26533
  for (const filename of ADMIN_AGENT_FILES) {
26425
- const overridePath = resolve31(accountDir, "agents", "admin", filename);
26426
- const bundledPath = resolve31(PLATFORM_ROOT, "templates", "agents", "admin", filename);
26534
+ const overridePath = resolve32(accountDir, "agents", "admin", filename);
26535
+ const bundledPath = resolve32(PLATFORM_ROOT, "templates", "agents", "admin", filename);
26427
26536
  const labelStem = filename.replace(/\.md$/, "");
26428
26537
  const row = await readAgentTemplateRow({
26429
26538
  id: `agent-template:admin:${filename}`,
@@ -26436,12 +26545,12 @@ async function fetchAgentTemplateRows(accountDir) {
26436
26545
  });
26437
26546
  if (row) rows.push(row);
26438
26547
  }
26439
- const overrideDir = resolve31(accountDir, "specialists", "agents");
26440
- const bundledDir = resolve31(PLATFORM_ROOT, "templates", "specialists", "agents");
26548
+ const overrideDir = resolve32(accountDir, "specialists", "agents");
26549
+ const bundledDir = resolve32(PLATFORM_ROOT, "templates", "specialists", "agents");
26441
26550
  const specialistNames = await unionSpecialistFilenames(overrideDir, bundledDir);
26442
26551
  for (const filename of specialistNames) {
26443
- const overridePath = resolve31(overrideDir, filename);
26444
- const bundledPath = resolve31(bundledDir, filename);
26552
+ const overridePath = resolve32(overrideDir, filename);
26553
+ const bundledPath = resolve32(bundledDir, filename);
26445
26554
  const row = await readAgentTemplateRow({
26446
26555
  id: `agent-template:specialist:${filename}`,
26447
26556
  displayName: filename.replace(/\.md$/, ""),
@@ -26458,11 +26567,11 @@ async function fetchAgentTemplateRows(accountDir) {
26458
26567
  async function unionSpecialistFilenames(overrideDir, bundledDir) {
26459
26568
  const names = /* @__PURE__ */ new Set();
26460
26569
  for (const dir of [overrideDir, bundledDir]) {
26461
- if (!existsSync39(dir)) continue;
26570
+ if (!existsSync38(dir)) continue;
26462
26571
  try {
26463
26572
  const entries2 = await readdir5(dir);
26464
- for (const entry of entries2) {
26465
- if (entry.endsWith(".md")) names.add(entry);
26573
+ for (const entry2 of entries2) {
26574
+ if (entry2.endsWith(".md")) names.add(entry2);
26466
26575
  }
26467
26576
  } catch (err) {
26468
26577
  const message = err instanceof Error ? err.message : String(err);
@@ -26473,7 +26582,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
26473
26582
  }
26474
26583
  async function readAgentTemplateRow(inp) {
26475
26584
  let chosenPath = null;
26476
- if (existsSync39(inp.overridePath)) {
26585
+ if (existsSync38(inp.overridePath)) {
26477
26586
  try {
26478
26587
  validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
26479
26588
  chosenPath = inp.overridePath;
@@ -26484,7 +26593,7 @@ async function readAgentTemplateRow(inp) {
26484
26593
  );
26485
26594
  return null;
26486
26595
  }
26487
- } else if (existsSync39(inp.bundledPath)) {
26596
+ } else if (existsSync38(inp.bundledPath)) {
26488
26597
  if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
26489
26598
  console.error(
26490
26599
  `[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
@@ -26944,24 +27053,24 @@ init_session_store();
26944
27053
  import { randomUUID as randomUUID9 } from "crypto";
26945
27054
 
26946
27055
  // server/wa-descriptor.ts
26947
- import { resolve as resolve32, join as join46 } from "path";
27056
+ import { resolve as resolve33, join as join46 } from "path";
26948
27057
  function waGatewayUrl() {
26949
27058
  return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
26950
27059
  }
26951
27060
  function waServerPath() {
26952
- return process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve32(process.env.MAXY_PLATFORM_ROOT ?? join46(__dirname, ".."), "services/whatsapp-channel/dist/server.js");
27061
+ return process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve33(process.env.MAXY_PLATFORM_ROOT ?? join46(__dirname, ".."), "services/whatsapp-channel/dist/server.js");
26953
27062
  }
26954
27063
  function waChannelDescriptor(senderId) {
26955
27064
  return { senderId, gatewayUrl: waGatewayUrl(), serverPath: waServerPath() };
26956
27065
  }
26957
27066
 
26958
27067
  // server/telegram-descriptor.ts
26959
- import { resolve as resolve33, join as join47 } from "path";
27068
+ import { resolve as resolve34, join as join47 } from "path";
26960
27069
  function telegramGatewayUrl() {
26961
27070
  return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
26962
27071
  }
26963
27072
  function telegramServerPath() {
26964
- return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve33(process.env.MAXY_PLATFORM_ROOT ?? join47(__dirname, ".."), "services/telegram-channel/dist/server.js");
27073
+ return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve34(process.env.MAXY_PLATFORM_ROOT ?? join47(__dirname, ".."), "services/telegram-channel/dist/server.js");
26965
27074
  }
26966
27075
  function telegramChannelDescriptor(senderId) {
26967
27076
  return { senderId, gatewayUrl: telegramGatewayUrl(), serverPath: telegramServerPath() };
@@ -27257,23 +27366,23 @@ var system_stats_default = app44;
27257
27366
  // server/routes/admin/health.ts
27258
27367
  init_dist();
27259
27368
  init_neo4j_store();
27260
- import { existsSync as existsSync40, readFileSync as readFileSync39 } from "fs";
27261
- import { resolve as resolve34, join as join48 } from "path";
27262
- var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve34(process.cwd(), "..");
27369
+ import { existsSync as existsSync39, readFileSync as readFileSync39 } from "fs";
27370
+ import { resolve as resolve35, join as join48 } from "path";
27371
+ var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve35(process.cwd(), "..");
27263
27372
  var brandHostname = "maxy";
27264
27373
  var brandJsonPath = join48(PLATFORM_ROOT4, "config", "brand.json");
27265
- if (existsSync40(brandJsonPath)) {
27374
+ if (existsSync39(brandJsonPath)) {
27266
27375
  try {
27267
27376
  const brand = JSON.parse(readFileSync39(brandJsonPath, "utf-8"));
27268
27377
  if (brand.hostname) brandHostname = brand.hostname;
27269
27378
  } catch {
27270
27379
  }
27271
27380
  }
27272
- var VERSION_FILE = resolve34(PLATFORM_ROOT4, `config/.${brandHostname}-version`);
27381
+ var VERSION_FILE = resolve35(PLATFORM_ROOT4, `config/.${brandHostname}-version`);
27273
27382
  var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
27274
27383
  var PROBE_TIMEOUT_MS = 1e3;
27275
27384
  function readVersion() {
27276
- if (!existsSync40(VERSION_FILE)) return "unknown";
27385
+ if (!existsSync39(VERSION_FILE)) return "unknown";
27277
27386
  return readFileSync39(VERSION_FILE, "utf-8").trim() || "unknown";
27278
27387
  }
27279
27388
  async function probeConversationDb() {
@@ -27330,17 +27439,17 @@ import { randomUUID as randomUUID10 } from "crypto";
27330
27439
 
27331
27440
  // ../lib/account-ingest-token/src/index.ts
27332
27441
  import { randomBytes, timingSafeEqual as timingSafeEqual2 } from "crypto";
27333
- import { existsSync as existsSync41, readdirSync as readdirSync25, readFileSync as readFileSync40, writeFileSync as writeFileSync16 } from "fs";
27334
- import { resolve as resolve35 } from "path";
27442
+ import { existsSync as existsSync40, readdirSync as readdirSync25, readFileSync as readFileSync40, writeFileSync as writeFileSync16 } from "fs";
27443
+ import { resolve as resolve36 } from "path";
27335
27444
  var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
27336
27445
  var INGEST_TOKEN_FIELD = "ingestToken";
27337
27446
  var TOKEN_RE = /^[0-9a-f]{64}$/;
27338
27447
  function configPath2(accountsDir, accountId) {
27339
- return resolve35(accountsDir, accountId, "account.json");
27448
+ return resolve36(accountsDir, accountId, "account.json");
27340
27449
  }
27341
27450
  function readConfig2(accountsDir, accountId) {
27342
27451
  const path = configPath2(accountsDir, accountId);
27343
- if (!existsSync41(path)) return null;
27452
+ if (!existsSync40(path)) return null;
27344
27453
  try {
27345
27454
  const parsed = JSON.parse(readFileSync40(path, "utf-8"));
27346
27455
  return parsed !== null && typeof parsed === "object" ? parsed : null;
@@ -27353,7 +27462,7 @@ function readIngestToken(accountsDir, accountId) {
27353
27462
  return typeof value === "string" && TOKEN_RE.test(value) ? value : null;
27354
27463
  }
27355
27464
  function listAccountIdsOnDisk(accountsDir) {
27356
- if (!existsSync41(accountsDir)) return [];
27465
+ if (!existsSync40(accountsDir)) return [];
27357
27466
  let entries2;
27358
27467
  try {
27359
27468
  entries2 = readdirSync25(accountsDir);
@@ -27361,7 +27470,7 @@ function listAccountIdsOnDisk(accountsDir) {
27361
27470
  return [];
27362
27471
  }
27363
27472
  return entries2.filter(
27364
- (name) => UUID_RE4.test(name) && existsSync41(resolve35(accountsDir, name, "account.json"))
27473
+ (name) => UUID_RE4.test(name) && existsSync40(resolve36(accountsDir, name, "account.json"))
27365
27474
  );
27366
27475
  }
27367
27476
  function resolveAccountIdByIngestToken(accountsDir, token) {
@@ -27498,6 +27607,7 @@ async function managerRcRecover(opts) {
27498
27607
  body: JSON.stringify({
27499
27608
  sessionId: opts.sessionId,
27500
27609
  recover: true,
27610
+ subscriberAttached: opts.subscriberAttached,
27501
27611
  role: "admin",
27502
27612
  channel: "webchat",
27503
27613
  senderId: opts.webchatChannel.key,
@@ -28215,7 +28325,7 @@ init_helpers();
28215
28325
  init_claude_agent();
28216
28326
  init_account_scope();
28217
28327
  init_neo4j_store();
28218
- import { existsSync as existsSync42 } from "fs";
28328
+ import { existsSync as existsSync41 } from "fs";
28219
28329
  import { randomUUID as randomUUID11 } from "crypto";
28220
28330
  import { join as join50 } from "path";
28221
28331
 
@@ -28885,7 +28995,7 @@ app53.get("/booking-link", requireAdminSession, (c) => {
28885
28995
  }
28886
28996
  const id82 = account.accountId.slice(0, 8);
28887
28997
  try {
28888
- if (!existsSync42(join50(account.accountDir, AVAILABILITY_FILENAME))) {
28998
+ if (!existsSync41(join50(account.accountDir, AVAILABILITY_FILENAME))) {
28889
28999
  console.log(`[calendar] op=booking-link accountId=${id82} bookingDomain=none source=availability-config`);
28890
29000
  return c.json({ bookingDomain: null });
28891
29001
  }
@@ -28907,7 +29017,7 @@ var calendar_default = app53;
28907
29017
  init_dist();
28908
29018
  init_helpers();
28909
29019
  init_account_scope();
28910
- import { existsSync as existsSync43, readFileSync as readFileSync42 } from "fs";
29020
+ import { existsSync as existsSync42, readFileSync as readFileSync42 } from "fs";
28911
29021
  import { join as join51 } from "path";
28912
29022
  var app54 = new Hono();
28913
29023
  var PORTAL_FILENAME = "data-portal.json";
@@ -28920,7 +29030,7 @@ app54.get("/portal-link", requireAdminSession, (c) => {
28920
29030
  const id82 = account.accountId.slice(0, 8);
28921
29031
  try {
28922
29032
  const path = join51(account.accountDir, PORTAL_FILENAME);
28923
- if (!existsSync43(path)) {
29033
+ if (!existsSync42(path)) {
28924
29034
  console.log(`[data-portal] op=portal-link accountId=${id82} portalDomain=none source=data-portal-config`);
28925
29035
  return c.json({ portalDomain: null });
28926
29036
  }
@@ -28945,10 +29055,12 @@ init_neo4j_store();
28945
29055
  init_src2();
28946
29056
  import cronParser from "cron-parser";
28947
29057
  import { int as int2 } from "neo4j-driver";
29058
+ import { readFileSync as readFileSync44 } from "fs";
29059
+ import { resolve as resolve38 } from "path";
28948
29060
 
28949
29061
  // ../lib/agent-dispatch-rule/src/index.ts
28950
29062
  import { readFileSync as readFileSync43 } from "fs";
28951
- import { resolve as resolve36 } from "path";
29063
+ import { resolve as resolve37 } from "path";
28952
29064
  function normalizeE1642(value) {
28953
29065
  let candidate = value.trim().replace(/[\s\-().]/g, "");
28954
29066
  candidate = candidate.replace(/^whatsapp:/i, "");
@@ -28965,7 +29077,7 @@ function phonesMatch2(a, b) {
28965
29077
  function readChannelAdmins(accountDir, channel) {
28966
29078
  let config = {};
28967
29079
  try {
28968
- config = JSON.parse(readFileSync43(resolve36(accountDir, "account.json"), "utf-8"));
29080
+ config = JSON.parse(readFileSync43(resolve37(accountDir, "account.json"), "utf-8"));
28969
29081
  } catch {
28970
29082
  config = {};
28971
29083
  }
@@ -29022,7 +29134,7 @@ function boundSubAccountFor(accountDir, destination) {
29022
29134
  }
29023
29135
  function validateAgentDestination(accountsDir, houseAccountId, creatingAccountId, channel, destination) {
29024
29136
  if (channel === "whatsapp") {
29025
- const houseDir = resolve36(accountsDir, houseAccountId);
29137
+ const houseDir = resolve37(accountsDir, houseAccountId);
29026
29138
  const registered = registeredAccountForDestination(houseDir, houseAccountId, destination);
29027
29139
  if (registered === null) return { valid: false, reason: "not-registered" };
29028
29140
  if (creatingAccountId === houseAccountId) return { valid: true };
@@ -29034,7 +29146,7 @@ function validateAgentDestination(accountsDir, houseAccountId, creatingAccountId
29034
29146
  scheduleAccount: creatingAccountId
29035
29147
  };
29036
29148
  }
29037
- const { adminUsers } = readChannelAdmins(resolve36(accountsDir, creatingAccountId), "telegram");
29149
+ const { adminUsers } = readChannelAdmins(resolve37(accountsDir, creatingAccountId), "telegram");
29038
29150
  const asNumber = Number(destination);
29039
29151
  const ok2 = Number.isInteger(asNumber) && adminUsers.includes(asNumber);
29040
29152
  return ok2 ? { valid: true } : { valid: false, reason: "not-registered" };
@@ -29044,7 +29156,7 @@ function listDispatchDestinations(input) {
29044
29156
  const managers = [];
29045
29157
  const admins = [];
29046
29158
  if (houseAccountId !== null) {
29047
- const houseDir = resolve36(accountsDir, houseAccountId);
29159
+ const houseDir = resolve37(accountsDir, houseAccountId);
29048
29160
  const { adminPhones, accountManagers } = readChannelAdmins(houseDir, "whatsapp");
29049
29161
  const seen = /* @__PURE__ */ new Set();
29050
29162
  for (const [phone, subAccountId] of Object.entries(accountManagers)) {
@@ -29062,7 +29174,7 @@ function listDispatchDestinations(input) {
29062
29174
  admins.push({ channel: "whatsapp", destination: phone, label: "House admin" });
29063
29175
  }
29064
29176
  }
29065
- const { byBot: byBot2 } = readChannelAdmins(resolve36(accountsDir, accountId), "telegram");
29177
+ const { byBot: byBot2 } = readChannelAdmins(resolve37(accountsDir, accountId), "telegram");
29066
29178
  const telegram = byBot2.flatMap((b) => b.adminUsers.map((id) => ({ botId: b.botId, id }))).sort((a, b) => a.id - b.id || a.botId.localeCompare(b.botId)).map(({ botId, id }) => ({
29067
29179
  channel: "telegram",
29068
29180
  destination: String(id),
@@ -29112,6 +29224,23 @@ var ROUTINE_COLS = `e.eventId AS eventId, e.name AS name, e.eventStatus AS event
29112
29224
  function since24h() {
29113
29225
  return new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
29114
29226
  }
29227
+ function gateClassification(row) {
29228
+ const judged = row.eventStatus === "scheduled" && row.recurrence !== null && row.agentChannel !== null && row.agentDestination !== null && row.agentPrompt !== null;
29229
+ if (!judged) return null;
29230
+ if (row.gatePlugin) return true;
29231
+ return typeof row.gateWaived === "string" && row.gateWaived.trim() !== "";
29232
+ }
29233
+ function gateWatermarkPath(eventId) {
29234
+ return resolve38(PLATFORM_ROOT, "..", "state", "scheduling-gate", `${eventId}.json`);
29235
+ }
29236
+ function readGateLastCommitAt(eventId) {
29237
+ try {
29238
+ const parsed = JSON.parse(readFileSync44(gateWatermarkPath(eventId), "utf-8"));
29239
+ return typeof parsed?.lastCommitAt === "string" ? parsed.lastCommitAt : null;
29240
+ } catch {
29241
+ return null;
29242
+ }
29243
+ }
29115
29244
  function mapRoutine(r) {
29116
29245
  const g = (k) => r.get(k) ?? null;
29117
29246
  const n = (k) => {
@@ -29161,7 +29290,23 @@ function mapRoutine(r) {
29161
29290
  // only the list: the modal's save replaces the list row wholesale
29162
29291
  // (page.tsx:202), so counts served on the list alone would blank on save.
29163
29292
  runs24h: n("runs24h"),
29164
- gated24h: n("gated24h")
29293
+ gated24h: n("gated24h"),
29294
+ // Task 2472 — computed from columns already projected above, so no Cypher
29295
+ // changes and no second query. See gateClassification for why `null` is a
29296
+ // third state rather than a default `true`.
29297
+ gateClassified: gateClassification({
29298
+ eventStatus: g("eventStatus"),
29299
+ recurrence: g("recurrence"),
29300
+ agentChannel: g("agentChannel"),
29301
+ agentDestination: g("agentDestination"),
29302
+ agentPrompt: g("agentPrompt"),
29303
+ gatePlugin: g("gatePlugin"),
29304
+ gateWaived: g("gateWaived")
29305
+ }),
29306
+ // Task 2472 — read only for a gated row: an ungated one has no gate to be
29307
+ // late, and a file read per card on every list would otherwise be spent
29308
+ // proving that.
29309
+ gateLastCommitAt: g("gateTool") === null ? null : readGateLastCommitAt(g("eventId") ?? "")
29165
29310
  };
29166
29311
  }
29167
29312
  function dispatchFieldsOf(r) {
@@ -29919,8 +30064,8 @@ init_dist();
29919
30064
  init_helpers();
29920
30065
  init_account_scope();
29921
30066
  init_route_cache();
29922
- import { existsSync as existsSync44, readdirSync as readdirSync26, readFileSync as readFileSync44, rmSync as rmSync6 } from "fs";
29923
- import { resolve as resolve37 } from "path";
30067
+ import { existsSync as existsSync43, readdirSync as readdirSync26, readFileSync as readFileSync45, rmSync as rmSync6 } from "fs";
30068
+ import { resolve as resolve39 } from "path";
29924
30069
  var app56 = new Hono();
29925
30070
  var NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
29926
30071
  function splitSkillMd(text) {
@@ -29943,7 +30088,7 @@ function stripQuotes(s) {
29943
30088
  return s;
29944
30089
  }
29945
30090
  function subdirs2(dir) {
29946
- if (!existsSync44(dir)) return [];
30091
+ if (!existsSync43(dir)) return [];
29947
30092
  try {
29948
30093
  return readdirSync26(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
29949
30094
  } catch {
@@ -29958,19 +30103,19 @@ app56.get("/", requireAdminSession, async (c) => {
29958
30103
  }
29959
30104
  ensureAccountWatchers(acct.accountId, acct.accountDir);
29960
30105
  const { value, state } = await getOrCompute("skills", acct.accountId, async () => {
29961
- const pluginsDir = resolve37(acct.accountDir, "plugins");
30106
+ const pluginsDir = resolve39(acct.accountDir, "plugins");
29962
30107
  const skills = [];
29963
30108
  let pluginCount = 0;
29964
30109
  for (const plugin of subdirs2(pluginsDir)) {
29965
30110
  pluginCount++;
29966
- const skillsDir = resolve37(pluginsDir, plugin, "skills");
30111
+ const skillsDir = resolve39(pluginsDir, plugin, "skills");
29967
30112
  for (const skill of subdirs2(skillsDir)) {
29968
- const skillMd = resolve37(skillsDir, skill, "SKILL.md");
29969
- if (!existsSync44(skillMd)) continue;
30113
+ const skillMd = resolve39(skillsDir, skill, "SKILL.md");
30114
+ if (!existsSync43(skillMd)) continue;
29970
30115
  try {
29971
- const { frontmatter } = splitSkillMd(readFileSync44(skillMd, "utf-8"));
29972
- const refsDir = resolve37(skillsDir, skill, "references");
29973
- const refCount = existsSync44(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).length : 0;
30116
+ const { frontmatter } = splitSkillMd(readFileSync45(skillMd, "utf-8"));
30117
+ const refsDir = resolve39(skillsDir, skill, "references");
30118
+ const refCount = existsSync43(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).length : 0;
29974
30119
  skills.push({ plugin, skill, description: frontmatter.description, refCount });
29975
30120
  } catch (err) {
29976
30121
  console.error(`[admin:skills] op=list-skip plugin=${plugin} skill=${skill} reason="${err instanceof Error ? err.message : String(err)}"`);
@@ -29992,15 +30137,15 @@ app56.get("/:plugin/:skill", requireAdminSession, async (c) => {
29992
30137
  console.error(`[admin:skills] op=get outcome=rejected reason=invalid-name plugin="${plugin}" skill="${skill}"`);
29993
30138
  return c.json({ error: "Invalid plugin or skill name" }, 400);
29994
30139
  }
29995
- const skillDir = resolve37(acct.accountDir, "plugins", plugin, "skills", skill);
29996
- const skillMd = resolve37(skillDir, "SKILL.md");
29997
- const found = existsSync44(skillMd);
30140
+ const skillDir = resolve39(acct.accountDir, "plugins", plugin, "skills", skill);
30141
+ const skillMd = resolve39(skillDir, "SKILL.md");
30142
+ const found = existsSync43(skillMd);
29998
30143
  console.log(`[admin:skills] op=get plugin=${plugin} skill=${skill} found=${found}`);
29999
30144
  if (!found) return c.json({ error: "Skill not found" }, 404);
30000
30145
  try {
30001
- const { frontmatter, body } = splitSkillMd(readFileSync44(skillMd, "utf-8"));
30002
- const refsDir = resolve37(skillDir, "references");
30003
- const references = existsSync44(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name).sort() : [];
30146
+ const { frontmatter, body } = splitSkillMd(readFileSync45(skillMd, "utf-8"));
30147
+ const refsDir = resolve39(skillDir, "references");
30148
+ const references = existsSync43(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name).sort() : [];
30004
30149
  return c.json({ skill: { plugin, skill, frontmatter, body, references } });
30005
30150
  } catch (err) {
30006
30151
  const message = err instanceof Error ? err.message : String(err);
@@ -30017,10 +30162,10 @@ app56.delete("/:plugin/:skill", requireAdminSession, async (c) => {
30017
30162
  console.error(`[admin:skills] op=delete outcome=rejected reason=invalid-name plugin="${plugin}" skill="${skill}"`);
30018
30163
  return c.json({ error: "Invalid plugin or skill name" }, 400);
30019
30164
  }
30020
- const pluginDir = resolve37(acct.accountDir, "plugins", plugin);
30021
- const skillsDir = resolve37(pluginDir, "skills");
30022
- const skillDir = resolve37(skillsDir, skill);
30023
- if (!existsSync44(resolve37(skillDir, "SKILL.md"))) {
30165
+ const pluginDir = resolve39(acct.accountDir, "plugins", plugin);
30166
+ const skillsDir = resolve39(pluginDir, "skills");
30167
+ const skillDir = resolve39(skillsDir, skill);
30168
+ if (!existsSync43(resolve39(skillDir, "SKILL.md"))) {
30024
30169
  console.error(`[admin:skills] op=delete outcome=rejected reason=not-found plugin=${plugin} skill=${skill}`);
30025
30170
  return c.json({ error: "Skill not found" }, 404);
30026
30171
  }
@@ -30028,7 +30173,7 @@ app56.delete("/:plugin/:skill", requireAdminSession, async (c) => {
30028
30173
  rmSync6(skillDir, { recursive: true, force: true });
30029
30174
  const pluginDirRemoved = subdirs2(skillsDir).length === 0;
30030
30175
  if (pluginDirRemoved) rmSync6(pluginDir, { recursive: true, force: true });
30031
- const skillDirGone = !existsSync44(skillDir);
30176
+ const skillDirGone = !existsSync43(skillDir);
30032
30177
  if (!skillDirGone) {
30033
30178
  console.error(`[admin:skills] op=delete plugin=${plugin} skill=${skill} outcome=rejected reason=rm-incomplete skillDirGone=false`);
30034
30179
  return c.json({ error: "Delete did not remove the skill directory" }, 503);
@@ -30672,8 +30817,8 @@ init_neo4j_store();
30672
30817
  // app/lib/admin-roster.ts
30673
30818
  init_account();
30674
30819
  init_neo4j_store();
30675
- import { existsSync as existsSync45, readdirSync as readdirSync27, readFileSync as readFileSync45 } from "fs";
30676
- import { resolve as resolve38 } from "path";
30820
+ import { existsSync as existsSync44, readdirSync as readdirSync27, readFileSync as readFileSync46 } from "fs";
30821
+ import { resolve as resolve40 } from "path";
30677
30822
  function readHouseAdminUserIds(accountsDir = ACCOUNTS_DIR) {
30678
30823
  let entries2;
30679
30824
  try {
@@ -30681,11 +30826,11 @@ function readHouseAdminUserIds(accountsDir = ACCOUNTS_DIR) {
30681
30826
  } catch {
30682
30827
  return [];
30683
30828
  }
30684
- for (const entry of entries2) {
30685
- const cfgPath = resolve38(accountsDir, entry, "account.json");
30686
- if (!existsSync45(cfgPath)) continue;
30829
+ for (const entry2 of entries2) {
30830
+ const cfgPath = resolve40(accountsDir, entry2, "account.json");
30831
+ if (!existsSync44(cfgPath)) continue;
30687
30832
  try {
30688
- const cfg = JSON.parse(readFileSync45(cfgPath, "utf-8"));
30833
+ const cfg = JSON.parse(readFileSync46(cfgPath, "utf-8"));
30689
30834
  if (cfg.role !== "house") continue;
30690
30835
  if (!Array.isArray(cfg.admins)) return [];
30691
30836
  return cfg.admins.map(
@@ -31033,7 +31178,7 @@ init_claude_agent();
31033
31178
  init_account();
31034
31179
  init_neo4j_store();
31035
31180
  init_src2();
31036
- import { resolve as resolve39 } from "path";
31181
+ import { resolve as resolve41 } from "path";
31037
31182
  var TAG49 = "[admin:routines]";
31038
31183
  function scopeFor(d, houseDir, houseAccountId) {
31039
31184
  if (d.channel !== "whatsapp") return null;
@@ -31063,7 +31208,7 @@ app67.get("/", requireAdminSession, async (c) => {
31063
31208
  console.error(`${TAG49} op=destinations account=${accountId.slice(0, 8)} outcome=house-unresolved reason="${message}"`);
31064
31209
  }
31065
31210
  const destinations = listDispatchDestinations({ accountsDir: ACCOUNTS_DIR, houseAccountId, accountId });
31066
- const houseDir = houseAccountId === null ? null : resolve39(ACCOUNTS_DIR, houseAccountId);
31211
+ const houseDir = houseAccountId === null ? null : resolve41(ACCOUNTS_DIR, houseAccountId);
31067
31212
  const byScope = /* @__PURE__ */ new Map();
31068
31213
  for (const d of destinations) {
31069
31214
  const scope = houseDir === null || houseAccountId === null ? null : scopeFor(d, houseDir, houseAccountId);
@@ -31413,9 +31558,9 @@ init_dist();
31413
31558
 
31414
31559
  // app/lib/access-email.ts
31415
31560
  import { spawn as spawn2 } from "child_process";
31416
- import { resolve as resolve40 } from "path";
31417
- var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve40(process.cwd(), "..");
31418
- var SEND_SCRIPT = resolve40(
31561
+ import { resolve as resolve42 } from "path";
31562
+ var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve42(process.cwd(), "..");
31563
+ var SEND_SCRIPT = resolve42(
31419
31564
  PLATFORM_ROOT5,
31420
31565
  "plugins",
31421
31566
  "email",
@@ -31559,8 +31704,8 @@ var access_default = app72;
31559
31704
  // server/routes/sites.ts
31560
31705
  init_dist();
31561
31706
  init_claude_agent();
31562
- import { existsSync as existsSync46, readFileSync as readFileSync46, realpathSync as realpathSync8, statSync as statSync25 } from "fs";
31563
- import { resolve as resolve41 } from "path";
31707
+ import { existsSync as existsSync45, readFileSync as readFileSync47, realpathSync as realpathSync8, statSync as statSync25 } from "fs";
31708
+ import { resolve as resolve43 } from "path";
31564
31709
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
31565
31710
  var MIME = {
31566
31711
  ".html": "text/html; charset=utf-8",
@@ -31617,15 +31762,15 @@ app73.get("/:rel{.*}", (c) => {
31617
31762
  }
31618
31763
  segments.push(seg);
31619
31764
  }
31620
- const rootDir = resolve41(account.accountDir, "sites");
31621
- let filePath = segments.length === 0 ? rootDir : resolve41(rootDir, ...segments);
31765
+ const rootDir = resolve43(account.accountDir, "sites");
31766
+ let filePath = segments.length === 0 ? rootDir : resolve43(rootDir, ...segments);
31622
31767
  if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
31623
31768
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
31624
31769
  return c.text("Forbidden", 403);
31625
31770
  }
31626
31771
  let stat11;
31627
31772
  try {
31628
- stat11 = existsSync46(filePath) ? statSync25(filePath) : null;
31773
+ stat11 = existsSync45(filePath) ? statSync25(filePath) : null;
31629
31774
  } catch {
31630
31775
  stat11 = null;
31631
31776
  }
@@ -31638,13 +31783,13 @@ app73.get("/:rel{.*}", (c) => {
31638
31783
  return c.redirect(target, 301);
31639
31784
  }
31640
31785
  if (stat11?.isDirectory()) {
31641
- filePath = resolve41(filePath, "index.html");
31786
+ filePath = resolve43(filePath, "index.html");
31642
31787
  }
31643
31788
  if (!filePath.startsWith(rootDir + "/")) {
31644
31789
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
31645
31790
  return c.text("Forbidden", 403);
31646
31791
  }
31647
- if (!existsSync46(filePath)) {
31792
+ if (!existsSync45(filePath)) {
31648
31793
  console.error(`[sites] not-found path=${reqPath} status=404`);
31649
31794
  return c.text("Not found", 404);
31650
31795
  }
@@ -31663,7 +31808,7 @@ app73.get("/:rel{.*}", (c) => {
31663
31808
  }
31664
31809
  let body;
31665
31810
  try {
31666
- body = readFileSync46(realPath);
31811
+ body = readFileSync47(realPath);
31667
31812
  } catch (err) {
31668
31813
  const code = err?.code;
31669
31814
  if (code === "EISDIR") {
@@ -31705,7 +31850,7 @@ init_neo4j_store();
31705
31850
  // app/lib/visitor-token.ts
31706
31851
  init_paths();
31707
31852
  import { createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
31708
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync47, writeFileSync as writeFileSync17 } from "fs";
31853
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync48, writeFileSync as writeFileSync17 } from "fs";
31709
31854
  import { dirname as dirname16 } from "path";
31710
31855
  var TOKEN_PREFIX = "v1.";
31711
31856
  var SECRET_BYTES = 32;
@@ -31714,7 +31859,7 @@ var cachedSecret = null;
31714
31859
  function getSecret() {
31715
31860
  if (cachedSecret) return cachedSecret;
31716
31861
  try {
31717
- const hex2 = readFileSync47(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
31862
+ const hex2 = readFileSync48(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
31718
31863
  if (hex2.length === SECRET_BYTES * 2) {
31719
31864
  cachedSecret = Buffer.from(hex2, "hex");
31720
31865
  return cachedSecret;
@@ -31728,7 +31873,7 @@ function getSecret() {
31728
31873
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
31729
31874
  } catch {
31730
31875
  }
31731
- const hex = readFileSync47(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
31876
+ const hex = readFileSync48(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
31732
31877
  cachedSecret = Buffer.from(hex, "hex");
31733
31878
  return cachedSecret;
31734
31879
  }
@@ -31784,7 +31929,7 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
31784
31929
  init_dist();
31785
31930
 
31786
31931
  // app/lib/brand-config.ts
31787
- import { existsSync as existsSync47, readFileSync as readFileSync48 } from "fs";
31932
+ import { existsSync as existsSync46, readFileSync as readFileSync49 } from "fs";
31788
31933
  import { join as join53 } from "path";
31789
31934
  var cached2 = null;
31790
31935
  var cachedAttempted = false;
@@ -31794,9 +31939,9 @@ function readBrandConfig() {
31794
31939
  const platformRoot5 = process.env.MAXY_PLATFORM_ROOT;
31795
31940
  if (!platformRoot5) return null;
31796
31941
  const brandPath = join53(platformRoot5, "config", "brand.json");
31797
- if (!existsSync47(brandPath)) return null;
31942
+ if (!existsSync46(brandPath)) return null;
31798
31943
  try {
31799
- cached2 = JSON.parse(readFileSync48(brandPath, "utf-8"));
31944
+ cached2 = JSON.parse(readFileSync49(brandPath, "utf-8"));
31800
31945
  return cached2;
31801
31946
  } catch {
31802
31947
  return null;
@@ -32075,9 +32220,9 @@ function originAllowed(origin, allowlist) {
32075
32220
  try {
32076
32221
  const u = new URL(origin);
32077
32222
  const host = u.host;
32078
- return allowlist.some((entry) => {
32079
- if (entry === host) return true;
32080
- if (entry.startsWith("*.")) return host.endsWith(entry.slice(1));
32223
+ return allowlist.some((entry2) => {
32224
+ if (entry2 === host) return true;
32225
+ if (entry2.startsWith("*.")) return host.endsWith(entry2.slice(1));
32081
32226
  return false;
32082
32227
  });
32083
32228
  } catch {
@@ -32299,16 +32444,16 @@ var visitor_event_default = app76;
32299
32444
  // server/routes/session.ts
32300
32445
  init_dist();
32301
32446
  init_claude_agent();
32302
- import { resolve as resolve42 } from "path";
32303
- import { existsSync as existsSync48, writeFileSync as writeFileSync18, mkdirSync as mkdirSync10 } from "fs";
32447
+ import { resolve as resolve44 } from "path";
32448
+ import { existsSync as existsSync47, writeFileSync as writeFileSync18, mkdirSync as mkdirSync10 } from "fs";
32304
32449
  init_neo4j_store();
32305
32450
  init_paths();
32306
32451
  var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
32307
32452
  function writeBrandingCache(accountId, agentSlug, branding) {
32308
32453
  try {
32309
- const cacheDir = resolve42(MAXY_DIR, "branding-cache", accountId);
32454
+ const cacheDir = resolve44(MAXY_DIR, "branding-cache", accountId);
32310
32455
  mkdirSync10(cacheDir, { recursive: true });
32311
- writeFileSync18(resolve42(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
32456
+ writeFileSync18(resolve44(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
32312
32457
  } catch (err) {
32313
32458
  console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
32314
32459
  }
@@ -32389,8 +32534,8 @@ app77.post("/", async (c) => {
32389
32534
  }
32390
32535
  let agentConfig = null;
32391
32536
  if (account) {
32392
- const agentDir = resolve42(account.accountDir, "agents", agentSlug);
32393
- if (!existsSync48(agentDir) || !existsSync48(resolve42(agentDir, "config.json"))) {
32537
+ const agentDir = resolve44(account.accountDir, "agents", agentSlug);
32538
+ if (!existsSync47(agentDir) || !existsSync47(resolve44(agentDir, "config.json"))) {
32394
32539
  return c.json({ error: "Agent not found" }, 404);
32395
32540
  }
32396
32541
  agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
@@ -32574,7 +32719,7 @@ var calendar_public_default = app78;
32574
32719
  init_dist();
32575
32720
  import { createReadStream as createReadStream3 } from "fs";
32576
32721
  import { stat as stat8, readFile as readFile11, realpath } from "fs/promises";
32577
- import { join as join54, resolve as resolve43, sep as sep11 } from "path";
32722
+ import { join as join54, resolve as resolve45, sep as sep11 } from "path";
32578
32723
  import { Readable as Readable3 } from "stream";
32579
32724
  init_data_path();
32580
32725
  init_attachments();
@@ -32629,7 +32774,7 @@ ${expiresAtMs}`)
32629
32774
  return diff2 === 0;
32630
32775
  }
32631
32776
  async function readAccountSecret(accountId) {
32632
- const path = resolve43(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
32777
+ const path = resolve45(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
32633
32778
  let text;
32634
32779
  try {
32635
32780
  text = await readFile11(path, "utf8");
@@ -32665,7 +32810,7 @@ async function resolveFetchTarget(accountDir, relPath, ownerId) {
32665
32810
  const exposeFolders = await readExposeFolders2(accountDir);
32666
32811
  const { exposed } = resolveExposedDirs(schemaText, exposeFolders);
32667
32812
  if (exposed.length === 0 && !ownerId) return { ok: false, reason: "not-exposed" };
32668
- const lexical = resolve43(accountDir, relPath);
32813
+ const lexical = resolve45(accountDir, relPath);
32669
32814
  let root;
32670
32815
  let absolute;
32671
32816
  try {
@@ -32714,7 +32859,7 @@ async function handle(c, headOnly) {
32714
32859
  return c.json({ ok: false, error: "denied" }, 401);
32715
32860
  }
32716
32861
  const target = await resolveFetchTarget(
32717
- resolve43(DATA_ROOT, "accounts", accountId),
32862
+ resolve45(DATA_ROOT, "accounts", accountId),
32718
32863
  relPath,
32719
32864
  owner || void 0
32720
32865
  );
@@ -32836,7 +32981,7 @@ init_neo4j_store();
32836
32981
 
32837
32982
  // app/lib/ledger-census.ts
32838
32983
  init_loop_registry();
32839
- import { readdirSync as readdirSync29, readFileSync as readFileSync49, statSync as statSync26 } from "fs";
32984
+ import { readdirSync as readdirSync29, readFileSync as readFileSync50, statSync as statSync26 } from "fs";
32840
32985
  import { join as join55 } from "path";
32841
32986
 
32842
32987
  // ../lib/ledger-core/dist/reconcile.js
@@ -32894,7 +33039,7 @@ function countWriteRejects24h(logDir, now) {
32894
33039
  const path = join55(logDir, name);
32895
33040
  try {
32896
33041
  if (now - statSync26(path).mtimeMs > DAY_MS) continue;
32897
- for (const line of readFileSync49(path, "utf8").split("\n")) {
33042
+ for (const line of readFileSync50(path, "utf8").split("\n")) {
32898
33043
  if (line.includes("[graph-write] reject")) total += 1;
32899
33044
  }
32900
33045
  } catch {
@@ -32997,7 +33142,7 @@ function startLedgerCensus(openSession, opts = {}) {
32997
33142
  // app/lib/dispatch-census.ts
32998
33143
  init_loop_registry();
32999
33144
  init_account();
33000
- import { readFileSync as readFileSync50 } from "fs";
33145
+ import { readFileSync as readFileSync51 } from "fs";
33001
33146
  import { join as join56 } from "path";
33002
33147
  var CENSUS_TAG2 = "[dispatch-census]";
33003
33148
  var CONFIG_AUDIT_TAG = "[dispatch-config-audit]";
@@ -33031,7 +33176,7 @@ function percentile(sortedAsc, p) {
33031
33176
  function auditConfigFile(accountDir) {
33032
33177
  let cfg;
33033
33178
  try {
33034
- cfg = JSON.parse(readFileSync50(join56(accountDir, "dispatch", "config.json"), "utf-8"));
33179
+ cfg = JSON.parse(readFileSync51(join56(accountDir, "dispatch", "config.json"), "utf-8"));
33035
33180
  } catch {
33036
33181
  return null;
33037
33182
  }
@@ -33056,7 +33201,7 @@ function auditConfigFile(accountDir) {
33056
33201
  function terminalStatuses(accountDir) {
33057
33202
  try {
33058
33203
  const cfg = JSON.parse(
33059
- readFileSync50(join56(accountDir, "dispatch", "config.json"), "utf-8")
33204
+ readFileSync51(join56(accountDir, "dispatch", "config.json"), "utf-8")
33060
33205
  );
33061
33206
  return {
33062
33207
  job: (cfg.statuses?.job ?? []).filter((s) => s.terminal).map((s) => s.name),
@@ -33447,7 +33592,7 @@ function startGraphHealthTimer() {
33447
33592
  init_loop_registry();
33448
33593
  init_data_path();
33449
33594
  var import_dist11 = __toESM(require_dist(), 1);
33450
- import { existsSync as existsSync49, statSync as statSync27, readdirSync as readdirSync30 } from "fs";
33595
+ import { existsSync as existsSync48, statSync as statSync27, readdirSync as readdirSync30 } from "fs";
33451
33596
  import { join as join57 } from "path";
33452
33597
  var TAG57 = "[shared-census]";
33453
33598
  var WIRED_SURFACES = 3;
@@ -33467,7 +33612,7 @@ function countFiles(dir) {
33467
33612
  }
33468
33613
  function runSharedFolderCensus(dataRoot = DATA_ROOT) {
33469
33614
  const root = (0, import_dist11.sharedRoot)(dataRoot);
33470
- const exists = existsSync49(root);
33615
+ const exists = existsSync48(root);
33471
33616
  let isDir = false;
33472
33617
  if (exists) {
33473
33618
  try {
@@ -33493,7 +33638,7 @@ function startSharedFolderCensus() {
33493
33638
  // app/lib/file-watcher.ts
33494
33639
  init_data_path();
33495
33640
  import * as fsp2 from "fs/promises";
33496
- import { resolve as resolve44, sep as sep12 } from "path";
33641
+ import { resolve as resolve46, sep as sep12 } from "path";
33497
33642
  var ACCOUNT_UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
33498
33643
  var DEFAULT_COALESCE_MS = 500;
33499
33644
  var ROOTS = ["accounts"];
@@ -33512,7 +33657,7 @@ async function startFileWatcher(opts = {}) {
33512
33657
  const dropFn = opts.drop ?? dropFileIndex;
33513
33658
  for (const r of ROOTS) {
33514
33659
  try {
33515
- await fsp2.mkdir(resolve44(dataRoot, r), { recursive: true });
33660
+ await fsp2.mkdir(resolve46(dataRoot, r), { recursive: true });
33516
33661
  } catch (err) {
33517
33662
  console.error(
33518
33663
  `[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
@@ -33533,7 +33678,7 @@ async function startFileWatcher(opts = {}) {
33533
33678
  timers.set(relativePath, t);
33534
33679
  }
33535
33680
  async function runHook(relativePath, accountId) {
33536
- const absolute = resolve44(dataRoot, relativePath);
33681
+ const absolute = resolve46(dataRoot, relativePath);
33537
33682
  let exists = false;
33538
33683
  try {
33539
33684
  const st = await fsp2.stat(absolute);
@@ -33557,7 +33702,7 @@ async function startFileWatcher(opts = {}) {
33557
33702
  }
33558
33703
  }
33559
33704
  async function watchRoot(rootName) {
33560
- const absRoot = resolve44(dataRoot, rootName);
33705
+ const absRoot = resolve46(dataRoot, rootName);
33561
33706
  try {
33562
33707
  const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
33563
33708
  for await (const event of iter) {
@@ -33599,7 +33744,7 @@ init_data_path();
33599
33744
  init_neo4j_store();
33600
33745
  init_account();
33601
33746
  import { mkdir as mkdir5, readdir as readdir6, rename as rename4, rm as rm3 } from "fs/promises";
33602
- import { dirname as dirname17, relative as relative8, resolve as resolve45 } from "path";
33747
+ import { dirname as dirname17, relative as relative8, resolve as resolve47 } from "path";
33603
33748
  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;
33604
33749
  async function walkFiles(dir) {
33605
33750
  const out = [];
@@ -33610,7 +33755,7 @@ async function walkFiles(dir) {
33610
33755
  return out;
33611
33756
  }
33612
33757
  for (const e of entries2) {
33613
- const abs = resolve45(dir, e.name);
33758
+ const abs = resolve47(dir, e.name);
33614
33759
  if (e.isDirectory()) {
33615
33760
  out.push(...await walkFiles(abs));
33616
33761
  } else if (e.isFile()) {
@@ -33630,7 +33775,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
33630
33775
  let moved = 0;
33631
33776
  for (const oldAbs of files) {
33632
33777
  const suffix = relative8(srcDir, oldAbs);
33633
- const newAbs = resolve45(destDir, suffix);
33778
+ const newAbs = resolve47(destDir, suffix);
33634
33779
  await mkdir5(dirname17(newAbs), { recursive: true });
33635
33780
  await rename4(oldAbs, newAbs);
33636
33781
  moved++;
@@ -33657,7 +33802,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
33657
33802
  }
33658
33803
  async function migrateUploads(opts = {}) {
33659
33804
  const dataRoot = opts.dataRoot ?? DATA_ROOT;
33660
- const oldRoot = resolve45(dataRoot, "uploads");
33805
+ const oldRoot = resolve47(dataRoot, "uploads");
33661
33806
  let topEntries;
33662
33807
  try {
33663
33808
  topEntries = await readdir6(oldRoot, { withFileTypes: true });
@@ -33674,12 +33819,12 @@ async function migrateUploads(opts = {}) {
33674
33819
  let moved = 0;
33675
33820
  let skipped = 0;
33676
33821
  try {
33677
- for (const entry of topEntries) {
33678
- const name = entry.name;
33822
+ for (const entry2 of topEntries) {
33823
+ const name = entry2.name;
33679
33824
  if (ACCOUNT_UUID_RE4.test(name)) {
33680
33825
  moved += await relocateTree(
33681
- resolve45(oldRoot, name),
33682
- resolve45(dataRoot, "accounts", name, "uploads"),
33826
+ resolve47(oldRoot, name),
33827
+ resolve47(dataRoot, "accounts", name, "uploads"),
33683
33828
  dataRoot,
33684
33829
  name,
33685
33830
  session
@@ -33691,8 +33836,8 @@ async function migrateUploads(opts = {}) {
33691
33836
  continue;
33692
33837
  }
33693
33838
  moved += await relocateTree(
33694
- resolve45(oldRoot, "public"),
33695
- resolve45(dataRoot, "accounts", installAccountId, "uploads", "public"),
33839
+ resolve47(oldRoot, "public"),
33840
+ resolve47(dataRoot, "accounts", installAccountId, "uploads", "public"),
33696
33841
  dataRoot,
33697
33842
  null,
33698
33843
  // public uploads carry no graph nodes
@@ -33726,7 +33871,7 @@ init_pin_validator();
33726
33871
  init_account();
33727
33872
  init_neo4j_store();
33728
33873
  import { readdir as readdir7, readFile as readFile12, rename as rename5, writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
33729
- import { existsSync as existsSync50 } from "fs";
33874
+ import { existsSync as existsSync49 } from "fs";
33730
33875
  import { join as join58 } from "path";
33731
33876
  var ADMIN_ROLE2 = "admin";
33732
33877
  var WEBCHAT_CHANNEL2 = "webchat";
@@ -33756,7 +33901,7 @@ async function writeRaw(path, obj) {
33756
33901
  await rename5(tmp, path);
33757
33902
  } catch (err) {
33758
33903
  try {
33759
- if (existsSync50(tmp)) await unlink4(tmp);
33904
+ if (existsSync49(tmp)) await unlink4(tmp);
33760
33905
  } catch {
33761
33906
  }
33762
33907
  throw err;
@@ -33779,11 +33924,11 @@ async function collectLiveSidecars(projectsRoot) {
33779
33924
  } catch {
33780
33925
  continue;
33781
33926
  }
33782
- for (const entry of entries2) {
33783
- if (entry.isFile() && SESSION_META_RE.test(entry.name)) {
33784
- out.push(join58(slugDir, entry.name));
33785
- } else if (entry.isDirectory() && entry.name === "subagents") {
33786
- const subDir = join58(slugDir, entry.name);
33927
+ for (const entry2 of entries2) {
33928
+ if (entry2.isFile() && SESSION_META_RE.test(entry2.name)) {
33929
+ out.push(join58(slugDir, entry2.name));
33930
+ } else if (entry2.isDirectory() && entry2.name === "subagents") {
33931
+ const subDir = join58(slugDir, entry2.name);
33787
33932
  let subs;
33788
33933
  try {
33789
33934
  subs = await readdir7(subDir, { withFileTypes: true });
@@ -33880,7 +34025,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
33880
34025
  // app/lib/migrate-sidecar-account-ids.ts
33881
34026
  init_account();
33882
34027
  import { readdir as readdir8, readFile as readFile13, rename as rename6, writeFile as writeFile6, unlink as unlink5 } from "fs/promises";
33883
- import { existsSync as existsSync51 } from "fs";
34028
+ import { existsSync as existsSync50 } from "fs";
33884
34029
  import { join as join59 } from "path";
33885
34030
  var SESSION_META_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
33886
34031
  function projectSlugForCwd(cwd) {
@@ -33907,7 +34052,7 @@ async function writeRaw2(path, obj) {
33907
34052
  await rename6(tmp, path);
33908
34053
  } catch (err) {
33909
34054
  try {
33910
- if (existsSync51(tmp)) await unlink5(tmp);
34055
+ if (existsSync50(tmp)) await unlink5(tmp);
33911
34056
  } catch {
33912
34057
  }
33913
34058
  throw err;
@@ -33934,11 +34079,11 @@ async function collectSidecars(projectsRoot) {
33934
34079
  } catch {
33935
34080
  continue;
33936
34081
  }
33937
- for (const entry of entries2) {
33938
- if (entry.isFile() && SESSION_META_RE2.test(entry.name)) {
33939
- out.push({ path: join59(slugDir, entry.name), slug: slug.name });
33940
- } else if (entry.isDirectory() && (entry.name === "subagents" || entry.name === "archive")) {
33941
- const subDir = join59(slugDir, entry.name);
34082
+ for (const entry2 of entries2) {
34083
+ if (entry2.isFile() && SESSION_META_RE2.test(entry2.name)) {
34084
+ out.push({ path: join59(slugDir, entry2.name), slug: slug.name });
34085
+ } else if (entry2.isDirectory() && (entry2.name === "subagents" || entry2.name === "archive")) {
34086
+ const subDir = join59(slugDir, entry2.name);
33942
34087
  let subs;
33943
34088
  try {
33944
34089
  subs = await readdir8(subDir, { withFileTypes: true });
@@ -35326,12 +35471,20 @@ var InboundHub2 = class {
35326
35471
  /** Task-962 — terminal-drop one still-queued in-flight message by id. Returns
35327
35472
  * whether a matching queued entry was found and removed. */
35328
35473
  dropQueued(key2, messageId) {
35474
+ return this.takeQueued(key2, messageId) !== null;
35475
+ }
35476
+ /** Task 2466 — remove one still-queued in-flight message by id and return its
35477
+ * payload, so the caller can re-route it. `dropQueued` discards; this hands
35478
+ * over, and the two share this one removal so a message can never be both
35479
+ * dropped and re-routed. Null when no matching queued entry exists — a
35480
+ * message that has already drained, acked, or been dropped is not takeable. */
35481
+ takeQueued(key2, messageId) {
35329
35482
  const s = this.keys.get(key2);
35330
- if (!s) return false;
35483
+ if (!s) return null;
35331
35484
  const i = s.inFlight.findIndex((e) => e.payload.messageId === messageId && e.state === "queued");
35332
- if (i < 0) return false;
35333
- s.inFlight.splice(i, 1);
35334
- return true;
35485
+ if (i < 0) return null;
35486
+ const [entry2] = s.inFlight.splice(i, 1);
35487
+ return entry2.payload;
35335
35488
  }
35336
35489
  /** Whether a key currently has a live channel server attached. The gateway
35337
35490
  * uses this to decide whether an inbound needs a cold-start spawn/resume. */
@@ -35481,7 +35634,7 @@ function createWebchatChannelRoutes(deps) {
35481
35634
  }
35482
35635
 
35483
35636
  // app/lib/webchat/gateway/resolve-turn-answer.ts
35484
- import { existsSync as existsSync52, openSync as openSync9, readSync as readSync8, closeSync as closeSync9, readdirSync as readdirSync31 } from "fs";
35637
+ import { existsSync as existsSync51, openSync as openSync9, readSync as readSync8, closeSync as closeSync9, readdirSync as readdirSync31 } from "fs";
35485
35638
  import { join as join60 } from "path";
35486
35639
  function resolveTurnAnswer(p) {
35487
35640
  if (!SESSION_ID_RE2.test(p.sessionId)) return "";
@@ -35510,7 +35663,7 @@ function resolveTurnAnswer(p) {
35510
35663
  return segmentText(parts.join("\n\n")).join("\n\n");
35511
35664
  }
35512
35665
  function findJsonl(projectsRoot, sessionId) {
35513
- if (!existsSync52(projectsRoot)) return null;
35666
+ if (!existsSync51(projectsRoot)) return null;
35514
35667
  let slugs;
35515
35668
  try {
35516
35669
  slugs = readdirSync31(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -35519,7 +35672,7 @@ function findJsonl(projectsRoot, sessionId) {
35519
35672
  }
35520
35673
  for (const slug of slugs) {
35521
35674
  const live = join60(projectsRoot, slug, `${sessionId}.jsonl`);
35522
- if (existsSync52(live)) return live;
35675
+ if (existsSync51(live)) return live;
35523
35676
  }
35524
35677
  return null;
35525
35678
  }
@@ -35573,6 +35726,12 @@ var WebchatGateway = class _WebchatGateway {
35573
35726
  /** Task-962 — `${key}::${messageId}` for messages a recovery call has already
35574
35727
  * been issued for, so recovery is attempted at most once per message. */
35575
35728
  recoveryAttempted = /* @__PURE__ */ new Set();
35729
+ /** Task 2465 — which session each conversation key's child is running. Written
35730
+ * at spawn (the admin id is decided before the manager call, so it is already
35731
+ * known there) and at turn-end (which carries key AND sessionId, so the map
35732
+ * survives a UI restart for any child that has completed a turn). Read only by
35733
+ * auditKeyOrphans. */
35734
+ keySession = /* @__PURE__ */ new Map();
35576
35735
  /** Task-962 — guards against overlapping async recovery passes. */
35577
35736
  recovering = false;
35578
35737
  seq = 0;
@@ -35581,16 +35740,16 @@ var WebchatGateway = class _WebchatGateway {
35581
35740
  * The public /api/chat route awaits this to return the reply on the same SSE
35582
35741
  * response, preserving the pre-756 POST→single-blob browser contract. */
35583
35742
  awaitReply(key2, timeoutMs) {
35584
- return new Promise((resolve51) => {
35743
+ return new Promise((resolve53) => {
35585
35744
  const timer = setTimeout(() => {
35586
35745
  this.replyAwaiters.delete(key2);
35587
- resolve51({ timeout: true });
35746
+ resolve53({ timeout: true });
35588
35747
  }, timeoutMs);
35589
35748
  if (timer.unref) timer.unref();
35590
35749
  this.replyAwaiters.set(key2, (r) => {
35591
35750
  clearTimeout(timer);
35592
35751
  this.replyAwaiters.delete(key2);
35593
- resolve51(r);
35752
+ resolve53(r);
35594
35753
  });
35595
35754
  });
35596
35755
  }
@@ -35601,11 +35760,11 @@ var WebchatGateway = class _WebchatGateway {
35601
35760
  * terminal dialog stays answerable). */
35602
35761
  awaitPermissionVerdict(p, timeoutMs) {
35603
35762
  const k = _WebchatGateway.promptKey(p.key, p.requestId);
35604
- return new Promise((resolve51) => {
35763
+ return new Promise((resolve53) => {
35605
35764
  this.pendingPrompts.get(k)?.resolve({ timeout: true });
35606
35765
  const timer = setTimeout(() => {
35607
35766
  this.pendingPrompts.delete(k);
35608
- resolve51({ timeout: true });
35767
+ resolve53({ timeout: true });
35609
35768
  }, timeoutMs);
35610
35769
  if (timer.unref) timer.unref();
35611
35770
  this.pendingPrompts.set(k, {
@@ -35615,7 +35774,7 @@ var WebchatGateway = class _WebchatGateway {
35615
35774
  clearTimeout(timer);
35616
35775
  this.pendingPrompts.delete(k);
35617
35776
  this.deps.onPointerChange?.(p.key);
35618
- resolve51(r);
35777
+ resolve53(r);
35619
35778
  }
35620
35779
  });
35621
35780
  console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
@@ -35626,9 +35785,9 @@ var WebchatGateway = class _WebchatGateway {
35626
35785
  * false (and is a no-op) when no prompt with this key+id is open — a late or
35627
35786
  * duplicate click. */
35628
35787
  resolvePermissionVerdict(key2, requestId, behavior) {
35629
- const entry = this.pendingPrompts.get(_WebchatGateway.promptKey(key2, requestId));
35630
- if (!entry) return false;
35631
- entry.resolve({ behavior });
35788
+ const entry2 = this.pendingPrompts.get(_WebchatGateway.promptKey(key2, requestId));
35789
+ if (!entry2) return false;
35790
+ entry2.resolve({ behavior });
35632
35791
  console.error(`[webchat:perm] op=verdict key=${keyDisplay(key2)} id=${requestId} behavior=${behavior}`);
35633
35792
  return true;
35634
35793
  }
@@ -35771,6 +35930,7 @@ var WebchatGateway = class _WebchatGateway {
35771
35930
  }
35772
35931
  },
35773
35932
  onTurnEnd: (key2, role, turn) => {
35933
+ this.keySession.set(key2, turn.sessionId);
35774
35934
  if (role === "admin") {
35775
35935
  this.lastDeliveredAt.delete(key2);
35776
35936
  return "none";
@@ -35801,6 +35961,7 @@ var WebchatGateway = class _WebchatGateway {
35801
35961
  });
35802
35962
  for (const e of events) {
35803
35963
  console.error(`[webchat:inbound] op=${e.kind} key=${e.key} messageId=${e.messageId} ageMs=${e.ageMs}`);
35964
+ this.deliveryFailures.set(e.key, { messageId: e.messageId, at: now });
35804
35965
  }
35805
35966
  return events;
35806
35967
  }
@@ -35830,6 +35991,7 @@ var WebchatGateway = class _WebchatGateway {
35830
35991
  run: () => {
35831
35992
  const now = Date.now();
35832
35993
  const events = this.reconcile(now);
35994
+ this.auditKeyOrphans(now);
35833
35995
  void this.recoverStalled(now);
35834
35996
  return events.length > 0 ? `${events.length} terminal events` : void 0;
35835
35997
  }
@@ -35846,6 +36008,60 @@ var WebchatGateway = class _WebchatGateway {
35846
36008
  clearDeliveryFailure(key2) {
35847
36009
  this.deliveryFailures.delete(key2);
35848
36010
  }
36011
+ /** Task 2465 — record which session a conversation key's child is running.
36012
+ * Public so the audit below can be driven directly in tests without standing
36013
+ * up the channel-server HTTP surface; production writes come from runSpawn and
36014
+ * the turn-end handler. */
36015
+ noteKeySession(key2, sessionId) {
36016
+ this.keySession.set(key2, sessionId);
36017
+ }
36018
+ /** Task 2465 — STANDING RECONCILIATION: a queued message on a key no live child
36019
+ * subscribes to, while the session that key names IS live under a different
36020
+ * key. Expected against actual, on the sweep tick.
36021
+ *
36022
+ * There is no action to hang this on and re-running the send reproduces the
36023
+ * same silence, which is why it is an audit rather than another action log.
36024
+ * Before this, the state emitted nothing until the 30s recovery tick, and the
36025
+ * queued key and the live key never appeared on one line anywhere in
36026
+ * server.log — the reason the 2026-08-06 incident read as a wedge throughout.
36027
+ * `liveKey` is the whole diagnosis, and it fires on the FIRST occurrence.
36028
+ *
36029
+ * Covers: a queued key with no subscriber whose named session is held by a
36030
+ * different attached key. Does NOT cover a live child this process neither
36031
+ * spawned nor saw finish a turn — it is absent from keySession, and this is
36032
+ * silent for it. Silent when the keys agree, which is every healthy send.
36033
+ *
36034
+ * Public for the same reason noteKeySession is.
36035
+ *
36036
+ * Task 2466 — the live-key scan moved to sessionSubscriberKey, which the
36037
+ * recovery pass also reads. The audit's own behaviour is unchanged. */
36038
+ auditKeyOrphans(now) {
36039
+ for (const c of this.hub.staleQueued(now, 0)) {
36040
+ const sessionId = c.key.startsWith("session:") ? c.key.slice("session:".length) : this.keySession.get(c.key);
36041
+ if (!sessionId) continue;
36042
+ const liveKey = this.sessionSubscriberKey(sessionId);
36043
+ if (liveKey === null || liveKey === c.key) continue;
36044
+ console.error(
36045
+ `[webchat:inbound] op=key-orphan key=${c.key} messageId=${c.messageId} ageMs=${c.ageMs} liveKey=${liveKey} sessionId=${sessionId.slice(0, 8)}`
36046
+ );
36047
+ }
36048
+ }
36049
+ /** Task 2466 — the conversation key whose channel server is attached right now
36050
+ * and which this gateway records as serving `sessionId`, or null when no such
36051
+ * key exists. The one place "is this session reachable over its channel" is
36052
+ * decided; the orphan audit above and the recovery pass below both read it.
36053
+ *
36054
+ * Both readers are recovery-shaped, so the caller always holds a key with no
36055
+ * subscriber and this can never return that same key. Blind, by construction,
36056
+ * to a live child this process neither spawned nor saw finish a turn: it is
36057
+ * absent from keySession, and the answer is null. */
36058
+ sessionSubscriberKey(sessionId) {
36059
+ for (const [liveKey, liveSession] of this.keySession) {
36060
+ if (liveSession !== sessionId) continue;
36061
+ if (this.hub.hasSubscriber(liveKey)) return liveKey;
36062
+ }
36063
+ return null;
36064
+ }
35849
36065
  /** Task-962 — recovery-aware pass over queued-no-subscriber messages.
35850
36066
  * - age in [RECOVER_TRIGGER_MS, DELIVER_FINAL_MS): ask the manager to recover
35851
36067
  * the live session ONCE. busy → leave queued for the channel server's
@@ -35874,15 +36090,19 @@ var WebchatGateway = class _WebchatGateway {
35874
36090
  const d = this.recoverable.get(c.key);
35875
36091
  if (!d || !this.deps.recoverLiveSession) {
35876
36092
  this.hub.dropQueued(c.key, c.messageId);
36093
+ this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
35877
36094
  console.error(
35878
36095
  `[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=no-descriptor`
35879
36096
  );
35880
36097
  continue;
35881
36098
  }
35882
36099
  this.recoveryAttempted.add(attemptKey);
36100
+ const recoverSessionId = c.key.startsWith("session:") ? c.key.slice("session:".length) : this.keySession.get(c.key) ?? d.sessionId;
36101
+ const liveKey = recoverSessionId === void 0 ? null : this.sessionSubscriberKey(recoverSessionId);
36102
+ const subscriberAttached = liveKey !== null;
35883
36103
  let r;
35884
36104
  try {
35885
- r = await this.deps.recoverLiveSession(d);
36105
+ r = await this.deps.recoverLiveSession({ ...d, subscriberAttached });
35886
36106
  } catch (err) {
35887
36107
  console.error(
35888
36108
  `[webchat:inbound] op=recover-error key=${keyDisplay(c.key)} messageId=${c.messageId} err=${err instanceof Error ? err.message : String(err)}`
@@ -35892,6 +36112,7 @@ var WebchatGateway = class _WebchatGateway {
35892
36112
  if (!r.live) {
35893
36113
  this.hub.dropQueued(c.key, c.messageId);
35894
36114
  this.recoveryAttempted.delete(attemptKey);
36115
+ this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
35895
36116
  console.error(
35896
36117
  `[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=not-live`
35897
36118
  );
@@ -35899,6 +36120,16 @@ var WebchatGateway = class _WebchatGateway {
35899
36120
  console.error(
35900
36121
  `[webchat:inbound] op=recover key=${keyDisplay(c.key)} messageId=${c.messageId} classification=${r.classification ?? "unknown"} respawned=${r.recovered === true}`
35901
36122
  );
36123
+ if (r.classification === "busy" && liveKey !== null && liveKey !== c.key) {
36124
+ const handed = this.hub.takeQueued(c.key, c.messageId);
36125
+ if (handed) {
36126
+ this.recoveryAttempted.delete(attemptKey);
36127
+ this.hub.deliver({ ...handed, key: liveKey }, now);
36128
+ console.error(
36129
+ `[webchat:inbound] op=recover-redeliver key=${c.key} liveKey=${liveKey} messageId=${c.messageId} ageMs=${c.ageMs}`
36130
+ );
36131
+ }
36132
+ }
35902
36133
  }
35903
36134
  }
35904
36135
  for (const key2 of this.recoverable.keys()) {
@@ -35963,6 +36194,10 @@ var WebchatGateway = class _WebchatGateway {
35963
36194
  accountId: input.accountId,
35964
36195
  key: input.key,
35965
36196
  role: input.role,
36197
+ // Task 2466 — a template value. Reachability is a fact about NOW, not
36198
+ // about when the message arrived, so recoverStalled re-stamps it on the
36199
+ // copy it hands to the manager.
36200
+ subscriberAttached: false,
35966
36201
  ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
35967
36202
  ...input.adminUserId !== void 0 ? { adminUserId: input.adminUserId } : {}
35968
36203
  });
@@ -36011,6 +36246,9 @@ var WebchatGateway = class _WebchatGateway {
36011
36246
  lastActivityAt: Date.now()
36012
36247
  });
36013
36248
  }
36249
+ if (input.role === "admin" && input.sessionId !== void 0) {
36250
+ this.keySession.set(input.key, input.sessionId);
36251
+ }
36014
36252
  } catch (err) {
36015
36253
  const reason = (err instanceof Error ? err.message : String(err)).replace(/\s+/g, " ");
36016
36254
  console.error(
@@ -36023,7 +36261,60 @@ var WebchatGateway = class _WebchatGateway {
36023
36261
  }
36024
36262
  };
36025
36263
 
36026
- // app/lib/channel-pty-bridge/follower.ts
36264
+ // app/lib/channel-delivery/answer-accumulator.ts
36265
+ var AnswerAccumulator = class {
36266
+ openId = "";
36267
+ buf = "";
36268
+ open = false;
36269
+ emitted = false;
36270
+ /** Feed one JSONL line. Returns every answer completed by this line, in order:
36271
+ * at most the previous message's, then this one's. */
36272
+ push(line) {
36273
+ if (!line) return [];
36274
+ let row;
36275
+ try {
36276
+ row = JSON.parse(line);
36277
+ } catch {
36278
+ return [];
36279
+ }
36280
+ if (row.type !== "assistant") return this.complete();
36281
+ const msg = row.message;
36282
+ const id = typeof msg?.id === "string" ? msg.id : "";
36283
+ const out = [];
36284
+ if (!this.open || id !== this.openId) {
36285
+ out.push(...this.complete());
36286
+ this.openId = id;
36287
+ this.buf = "";
36288
+ this.open = true;
36289
+ this.emitted = false;
36290
+ }
36291
+ if (Array.isArray(msg?.content)) {
36292
+ for (const block of msg.content) {
36293
+ if (block?.type === "text" && typeof block.text === "string") this.buf += block.text;
36294
+ }
36295
+ }
36296
+ if (!this.emitted && msg?.stop_reason === "end_turn" && this.buf.trim().length > 0) {
36297
+ this.emitted = true;
36298
+ out.push({ messageId: this.openId, text: this.buf });
36299
+ }
36300
+ return out;
36301
+ }
36302
+ /** Complete any open message. Called when the stream ends. */
36303
+ flush() {
36304
+ return this.complete();
36305
+ }
36306
+ complete() {
36307
+ if (!this.open) return [];
36308
+ const answer = !this.emitted && this.buf.trim().length > 0 ? { messageId: this.openId, text: this.buf } : null;
36309
+ this.open = false;
36310
+ this.openId = "";
36311
+ this.buf = "";
36312
+ this.emitted = false;
36313
+ return answer ? [answer] : [];
36314
+ }
36315
+ };
36316
+
36317
+ // app/lib/channel-delivery/emitter.ts
36027
36318
  function followerPendingMaxMs() {
36028
36319
  return Number(process.env.CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS ?? String(3e5));
36029
36320
  }
@@ -36037,19 +36328,31 @@ function toolResultText(content) {
36037
36328
  (b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : ""
36038
36329
  ).join("");
36039
36330
  }
36040
- function startFollower(opts) {
36331
+ function startEmitter(opts) {
36041
36332
  const abort2 = new AbortController();
36042
- const { entry, tag } = opts;
36333
+ const { entry: entry2, tag } = opts;
36334
+ const answers = new AnswerAccumulator();
36335
+ const emitAnswer = async (answer) => {
36336
+ if (answer.text.trim()) await fanOut2(entry2.subscribers, answer.text, opts.onError, tag);
36337
+ if (!opts.onAnswer) return;
36338
+ try {
36339
+ await opts.onAnswer(answer);
36340
+ } catch (err) {
36341
+ console.error(
36342
+ `${tag} answer-error sessionId=${entry2.sessionId.slice(0, 8)} messageId=${answer.messageId} message=${err instanceof Error ? err.message : String(err)}`
36343
+ );
36344
+ }
36345
+ };
36043
36346
  void (async () => {
36044
36347
  try {
36045
- const sid = entry.sessionId.slice(0, 8);
36348
+ const sid = entry2.sessionId.slice(0, 8);
36046
36349
  const deadline = Date.now() + followerPendingMaxMs();
36047
36350
  const retryMs = followerRetryMs();
36048
36351
  let res;
36049
36352
  let attempt = 0;
36050
36353
  for (; ; ) {
36051
36354
  res = await fetch(
36052
- managerLogFollowUrl(entry.sessionId, { boundary: opts.suppressResumeReplay === true }),
36355
+ managerLogFollowUrl(entry2.sessionId, { boundary: opts.suppressResumeReplay === true }),
36053
36356
  { signal: abort2.signal }
36054
36357
  );
36055
36358
  console.error(`${tag} follower-connect sessionId=${sid} status=${res.status}`);
@@ -36083,7 +36386,7 @@ function startFollower(opts) {
36083
36386
  const awaitingVerdict = /* @__PURE__ */ new Set();
36084
36387
  let firedFileTools = [];
36085
36388
  let suppressing = opts.suppressResumeReplay === true;
36086
- let discardedTurns = 0;
36389
+ let discardedAnswers = 0;
36087
36390
  let discardedFileTools = 0;
36088
36391
  while (!abort2.signal.aborted) {
36089
36392
  const { value, done } = await reader.read();
@@ -36100,7 +36403,7 @@ function startFollower(opts) {
36100
36403
  event = JSON.parse(line);
36101
36404
  } catch (err) {
36102
36405
  console.error(
36103
- `${tag} jsonl-parse-skip sessionId=${entry.sessionId.slice(0, 8)} bytes=${line.length} message=${err instanceof Error ? err.message : String(err)}`
36406
+ `${tag} jsonl-parse-skip sessionId=${entry2.sessionId.slice(0, 8)} bytes=${line.length} message=${err instanceof Error ? err.message : String(err)}`
36104
36407
  );
36105
36408
  continue;
36106
36409
  }
@@ -36108,11 +36411,18 @@ function startFollower(opts) {
36108
36411
  if (suppressing) {
36109
36412
  suppressing = false;
36110
36413
  console.error(
36111
- `${tag} follower-resume-boundary sessionId=${sid} discarded-head-turns=${discardedTurns} discarded-head-filetools=${discardedFileTools}`
36414
+ `${tag} emitter-resume-boundary sessionId=${sid} discarded-head-answers=${discardedAnswers} discarded-head-filetools=${discardedFileTools}`
36112
36415
  );
36113
36416
  }
36114
36417
  continue;
36115
36418
  }
36419
+ for (const answer of answers.push(line)) {
36420
+ if (suppressing) {
36421
+ discardedAnswers += 1;
36422
+ continue;
36423
+ }
36424
+ await emitAnswer(answer);
36425
+ }
36116
36426
  if (event.type === "user") {
36117
36427
  if (captureVerdicts && awaitingVerdict.size > 0 && !suppressing) {
36118
36428
  const umsg = event.message;
@@ -36136,7 +36446,6 @@ function startFollower(opts) {
36136
36446
  }
36137
36447
  }
36138
36448
  }
36139
- entry.pendingTurnText = "";
36140
36449
  firedFileTools = [];
36141
36450
  continue;
36142
36451
  }
@@ -36147,7 +36456,6 @@ function startFollower(opts) {
36147
36456
  if (Array.isArray(msg.content)) {
36148
36457
  for (const block of msg.content) {
36149
36458
  if (block?.type === "text" && typeof block.text === "string") {
36150
- entry.pendingTurnText += block.text;
36151
36459
  sawContent = true;
36152
36460
  } else if (block?.type === "tool_use") {
36153
36461
  sawContent = true;
@@ -36172,16 +36480,9 @@ function startFollower(opts) {
36172
36480
  }
36173
36481
  if (msg.stop_reason === "end_turn") {
36174
36482
  if (suppressing) {
36175
- entry.pendingTurnText = "";
36176
36483
  firedFileTools = [];
36177
- discardedTurns += 1;
36178
36484
  continue;
36179
36485
  }
36180
- const flush = entry.pendingTurnText;
36181
- entry.pendingTurnText = "";
36182
- if (flush.trim()) {
36183
- await fanOut2(entry.subscribers, flush, opts.onError, tag);
36184
- }
36185
36486
  if (fileDelivery && (firedFileTools.length > 0 || captureVerdicts)) {
36186
36487
  const fired = firedFileTools;
36187
36488
  firedFileTools = [];
@@ -36200,9 +36501,10 @@ function startFollower(opts) {
36200
36501
  }
36201
36502
  }
36202
36503
  }
36504
+ for (const answer of answers.flush()) await emitAnswer(answer);
36203
36505
  } catch (err) {
36204
36506
  if (!abort2.signal.aborted) {
36205
- opts.onError?.(`follower-error: ${err instanceof Error ? err.message : String(err)}`);
36507
+ opts.onError?.(`emitter-error: ${err instanceof Error ? err.message : String(err)}`);
36206
36508
  }
36207
36509
  } finally {
36208
36510
  opts.onClose();
@@ -36223,14 +36525,42 @@ async function fanOut2(subscribers, text, onError, tag) {
36223
36525
  );
36224
36526
  }
36225
36527
 
36528
+ // app/lib/channel-delivery/parity-census.ts
36529
+ var sessions2 = /* @__PURE__ */ new Map();
36530
+ function entry(sessionId) {
36531
+ let e = sessions2.get(sessionId);
36532
+ if (!e) {
36533
+ e = { rendered: /* @__PURE__ */ new Set(), sent: /* @__PURE__ */ new Set() };
36534
+ sessions2.set(sessionId, e);
36535
+ }
36536
+ return e;
36537
+ }
36538
+ function noteAnswer(sessionId, messageId) {
36539
+ entry(sessionId).rendered.add(messageId);
36540
+ }
36541
+ function noteSent(sessionId, messageId, channels) {
36542
+ if (channels.length === 0) return;
36543
+ entry(sessionId).sent.add(messageId);
36544
+ }
36545
+ function parityCensusLines() {
36546
+ const out = [];
36547
+ for (const [sessionId, e] of sessions2) {
36548
+ const missing = [...e.rendered].filter((id) => !e.sent.has(id));
36549
+ out.push(
36550
+ `[channel-parity] op=parity-census sessionId=${sessionId} rendered=${e.rendered.size} sent=${e.sent.size} missing=${missing.length} missingIds=${missing.join(",") || "none"}`
36551
+ );
36552
+ }
36553
+ return out;
36554
+ }
36555
+
36226
36556
  // app/lib/webchat/file-delivery.ts
36227
36557
  import { realpathSync as realpathSync9 } from "fs";
36228
- import { resolve as resolve46 } from "path";
36558
+ import { resolve as resolve48 } from "path";
36229
36559
 
36230
36560
  // app/lib/channel-pty-bridge/file-delivery.ts
36231
36561
  var SEND_USER_FILE = "SendUserFile";
36232
36562
  function makeFileDelivery(opts) {
36233
- const { entry, tag, channel, sendFile, deferUntilVerdict = false } = opts;
36563
+ const { entry: entry2, tag, channel, sendFile, deferUntilVerdict = false } = opts;
36234
36564
  let failedFiles = [];
36235
36565
  let attempts = 0;
36236
36566
  const pending = /* @__PURE__ */ new Map();
@@ -36257,7 +36587,7 @@ function makeFileDelivery(opts) {
36257
36587
  function unreconciledCall(reason, files, detail) {
36258
36588
  const d = detail ? ` detail="${detail.replace(/\s+/g, " ").slice(0, 120)}"` : "";
36259
36589
  console.error(
36260
- `${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${entry.sessionId.slice(0, 8)} tool=${SEND_USER_FILE} reason=${reason} files=${files}${d}`
36590
+ `${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${entry2.sessionId.slice(0, 8)} tool=${SEND_USER_FILE} reason=${reason} files=${files}${d}`
36261
36591
  );
36262
36592
  }
36263
36593
  const handler = {
@@ -36278,10 +36608,10 @@ function makeFileDelivery(opts) {
36278
36608
  const tried = attempts;
36279
36609
  failedFiles = [];
36280
36610
  attempts = 0;
36281
- const sid = entry.sessionId.slice(0, 8);
36611
+ const sid = entry2.sessionId.slice(0, 8);
36282
36612
  for (const file of failed2) {
36283
36613
  console.error(
36284
- `${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
36614
+ `${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
36285
36615
  );
36286
36616
  }
36287
36617
  if (deferUntilVerdict) {
@@ -36291,7 +36621,7 @@ function makeFileDelivery(opts) {
36291
36621
  }
36292
36622
  if (firedTools.includes(SEND_USER_FILE) && tried === 0) {
36293
36623
  console.error(
36294
- `${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
36624
+ `${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
36295
36625
  );
36296
36626
  }
36297
36627
  }
@@ -36320,43 +36650,43 @@ var TAG58 = "[webchat-adaptor]";
36320
36650
  function platformRoot2() {
36321
36651
  return process.env.MAXY_PLATFORM_ROOT || "";
36322
36652
  }
36323
- function makeWebchatSendFile(entry) {
36653
+ function makeWebchatSendFile(entry2) {
36324
36654
  return async (filePath) => {
36325
- if (!entry.accountId) {
36326
- console.error(`${TAG58} file-delivery reject reason=no-account sender=${entry.senderId}`);
36655
+ if (!entry2.accountId) {
36656
+ console.error(`${TAG58} file-delivery reject reason=no-account sender=${entry2.senderId}`);
36327
36657
  return { ok: false, error: "no-account" };
36328
36658
  }
36329
- const accountDir = resolve46(platformRoot2(), "..", "data/accounts", entry.accountId);
36659
+ const accountDir = resolve48(platformRoot2(), "..", "data/accounts", entry2.accountId);
36330
36660
  try {
36331
36661
  const resolved = realpathSync9(filePath);
36332
36662
  const accountResolved = realpathSync9(accountDir);
36333
36663
  if (!resolved.startsWith(accountResolved + "/")) {
36334
- console.error(`${TAG58} file-delivery reject reason=outside_account_directory sender=${entry.senderId}`);
36664
+ console.error(`${TAG58} file-delivery reject reason=outside_account_directory sender=${entry2.senderId}`);
36335
36665
  return { ok: false, error: "outside-account" };
36336
36666
  }
36337
36667
  return { ok: true };
36338
36668
  } catch (err) {
36339
36669
  const code = err.code;
36340
36670
  console.error(
36341
- `${TAG58} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry.senderId}`
36671
+ `${TAG58} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry2.senderId}`
36342
36672
  );
36343
36673
  return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
36344
36674
  }
36345
36675
  };
36346
36676
  }
36347
- function makeWebchatFileDelivery(entry) {
36677
+ function makeWebchatFileDelivery(entry2) {
36348
36678
  return makeFileDelivery({
36349
- entry,
36679
+ entry: entry2,
36350
36680
  tag: TAG58,
36351
36681
  channel: "webchat",
36352
- sendFile: makeWebchatSendFile(entry),
36682
+ sendFile: makeWebchatSendFile(entry2),
36353
36683
  deferUntilVerdict: true
36354
36684
  });
36355
36685
  }
36356
36686
 
36357
36687
  // app/lib/webchat/gateway/native-file-follower.ts
36358
36688
  function startWebchatNativeFileFollower(input) {
36359
- const entry = {
36689
+ const entry2 = {
36360
36690
  sessionId: input.sessionId,
36361
36691
  role: "admin",
36362
36692
  channel: "webchat",
@@ -36366,7 +36696,6 @@ function startWebchatNativeFileFollower(input) {
36366
36696
  lastInboundAt: Date.now(),
36367
36697
  writeChain: Promise.resolve(),
36368
36698
  subscribers: /* @__PURE__ */ new Set(),
36369
- pendingTurnText: "",
36370
36699
  followerAbort: null,
36371
36700
  followerRunning: false,
36372
36701
  sliceToken: "",
@@ -36375,13 +36704,24 @@ function startWebchatNativeFileFollower(input) {
36375
36704
  // Webchat renders from the transcript; there is no separate reply target.
36376
36705
  replyTarget: null
36377
36706
  };
36378
- return startFollower({
36379
- entry,
36707
+ return startEmitter({
36708
+ entry: entry2,
36380
36709
  tag: "[webchat-adaptor]",
36381
- fileDelivery: makeWebchatFileDelivery(entry),
36710
+ fileDelivery: makeWebchatFileDelivery(entry2),
36382
36711
  // A resumed session's JSONL already holds prior SendUserFile tool_uses;
36383
36712
  // suppress replay so historical calls are not re-reconciled on attach.
36384
36713
  suppressResumeReplay: true,
36714
+ // Task 2521 — the answer reaches every bound door through the one fan-out.
36715
+ // Ungated by design: whatever woke the turn, the reader and the chats show
36716
+ // the same thing.
36717
+ onAnswer: async (answer) => {
36718
+ const targets = input.fanTargets?.() ?? [];
36719
+ if (targets.length === 0) return;
36720
+ noteAnswer(input.sessionId, answer.messageId);
36721
+ const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
36722
+ noteSent(input.sessionId, answer.messageId, reached);
36723
+ console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
36724
+ },
36385
36725
  onClose: input.onClose ?? (() => {
36386
36726
  })
36387
36727
  });
@@ -36417,29 +36757,29 @@ var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
36417
36757
  function platformRoot3() {
36418
36758
  return process.env.MAXY_PLATFORM_ROOT || "";
36419
36759
  }
36420
- function makeWhatsAppSendFile(entry, maxyAccountId) {
36760
+ function makeWhatsAppSendFile(entry2, maxyAccountId) {
36421
36761
  return async (filePath, caption) => {
36422
36762
  const result = await sendWhatsAppDocument({
36423
- to: entry.senderId,
36763
+ to: entry2.senderId,
36424
36764
  filePath,
36425
36765
  caption,
36426
- accountId: entry.accountId,
36766
+ accountId: entry2.accountId,
36427
36767
  maxyAccountId,
36428
36768
  platformRoot: platformRoot3()
36429
36769
  });
36430
36770
  if (result.ok) return { ok: true };
36431
36771
  console.error(
36432
- `${TAG59} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
36772
+ `${TAG59} file-delivery reject reason=send-failed sender=${entry2.senderId} status=${result.status} message=${result.error}`
36433
36773
  );
36434
36774
  return { ok: false, error: result.error };
36435
36775
  };
36436
36776
  }
36437
- function makeWhatsAppFileDelivery(entry, maxyAccountId) {
36777
+ function makeWhatsAppFileDelivery(entry2, maxyAccountId) {
36438
36778
  const shared = makeFileDelivery({
36439
- entry,
36779
+ entry: entry2,
36440
36780
  tag: TAG59,
36441
36781
  channel: "whatsapp",
36442
- sendFile: makeWhatsAppSendFile(entry, maxyAccountId)
36782
+ sendFile: makeWhatsAppSendFile(entry2, maxyAccountId)
36443
36783
  });
36444
36784
  let turnStartedAt = null;
36445
36785
  let routeCalls = [];
@@ -36464,7 +36804,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
36464
36804
  const routes = routeCalls;
36465
36805
  turnStartedAt = null;
36466
36806
  routeCalls = [];
36467
- const sid = entry.sessionId.slice(0, 8);
36807
+ const sid = entry2.sessionId.slice(0, 8);
36468
36808
  shared.onTurnEnd(firedTools);
36469
36809
  for (const call of routes) {
36470
36810
  const routeAt = call.to !== void 0 && call.filePath !== void 0 ? routeDocumentOutboundAt(call.to, call.filePath) : void 0;
@@ -36472,7 +36812,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
36472
36812
  if (!delivered) {
36473
36813
  const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
36474
36814
  console.error(
36475
- `${TAG59} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
36815
+ `${TAG59} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
36476
36816
  );
36477
36817
  }
36478
36818
  }
@@ -36483,17 +36823,16 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
36483
36823
  // app/lib/whatsapp/gateway/native-file-follower.ts
36484
36824
  var COMPOSING_REFRESH_MS = 1e4;
36485
36825
  function startNativeFileFollower(input) {
36486
- const entry = {
36826
+ const entry2 = {
36487
36827
  sessionId: input.sessionId,
36488
- role: "admin",
36828
+ role: input.role,
36489
36829
  channel: "whatsapp",
36490
36830
  accountId: input.accountId,
36491
36831
  senderId: input.senderId,
36492
- agentSlug: "admin",
36832
+ agentSlug: input.role === "admin" ? "admin" : "default",
36493
36833
  lastInboundAt: Date.now(),
36494
36834
  writeChain: Promise.resolve(),
36495
36835
  subscribers: /* @__PURE__ */ new Set(),
36496
- pendingTurnText: "",
36497
36836
  followerAbort: null,
36498
36837
  followerRunning: false,
36499
36838
  sliceToken: "",
@@ -36529,15 +36868,28 @@ function startNativeFileFollower(input) {
36529
36868
  if (!sock) return;
36530
36869
  void sendPaused(sock, input.senderId);
36531
36870
  };
36532
- return startFollower({
36533
- entry,
36871
+ return startEmitter({
36872
+ entry: entry2,
36534
36873
  tag: "[whatsapp-adaptor]",
36535
- fileDelivery: makeWhatsAppFileDelivery(entry, input.maxyAccountId),
36874
+ // Task 2521 — admin-only. A public spawn has no tools (Task 2078), so a
36875
+ // handler here would reconcile a call that can never fire.
36876
+ fileDelivery: input.role === "admin" ? makeWhatsAppFileDelivery(entry2, input.maxyAccountId) : null,
36536
36877
  // A resumed session's JSONL already holds prior SendUserFile tool_uses;
36537
36878
  // suppress replay so historical files are not re-sent on attach.
36538
36879
  suppressResumeReplay: true,
36539
36880
  onActivity,
36540
36881
  onTurnComplete,
36882
+ // Task 2521 — the answer reaches every bound door through the one fan-out.
36883
+ // Ungated by design: whatever woke the turn, the reader and the chats show
36884
+ // the same thing.
36885
+ onAnswer: async (answer) => {
36886
+ const targets = input.fanTargets?.() ?? [];
36887
+ if (targets.length === 0) return;
36888
+ noteAnswer(input.sessionId, answer.messageId);
36889
+ const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
36890
+ noteSent(input.sessionId, answer.messageId, reached);
36891
+ console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
36892
+ },
36541
36893
  onClose: input.onClose ?? (() => {
36542
36894
  })
36543
36895
  });
@@ -37090,6 +37442,9 @@ function buildTelegramEnsureSession(deps) {
37090
37442
  accountId: input.accountId,
37091
37443
  chatId: input.chatId,
37092
37444
  botToken: input.botToken,
37445
+ // Task 2521 — resolved per answer, not captured once, so a door that goes
37446
+ // live after this session started still receives.
37447
+ fanTargets: () => deps.fanTargets(req.channelKey),
37093
37448
  // Identity-guarded eviction: only delete if this controller is still the
37094
37449
  // current one, so a stale self-close never removes a live follower.
37095
37450
  onClose: () => {
@@ -37104,7 +37459,7 @@ function buildTelegramEnsureSession(deps) {
37104
37459
  init_attachments();
37105
37460
  import { realpathSync as realpathSync10 } from "fs";
37106
37461
  import { readFile as readFile14, stat as stat10 } from "fs/promises";
37107
- import { resolve as resolve47, basename as basename16 } from "path";
37462
+ import { resolve as resolve49, basename as basename16 } from "path";
37108
37463
  var TAG61 = "[telegram:outbound]";
37109
37464
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
37110
37465
  async function sendTelegramDocument(input) {
@@ -37115,7 +37470,7 @@ async function sendTelegramDocument(input) {
37115
37470
  if (!maxyAccountId || !platformRoot5) {
37116
37471
  return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
37117
37472
  }
37118
- const accountDir = resolve47(platformRoot5, "..", "data/accounts", maxyAccountId);
37473
+ const accountDir = resolve49(platformRoot5, "..", "data/accounts", maxyAccountId);
37119
37474
  let accountResolved;
37120
37475
  try {
37121
37476
  accountResolved = realpathSync10(accountDir);
@@ -37184,39 +37539,39 @@ var TAG62 = "[telegram:outbound]";
37184
37539
  function platformRoot4() {
37185
37540
  return process.env.MAXY_PLATFORM_ROOT || "";
37186
37541
  }
37187
- function makeTelegramSendFile(entry, botToken) {
37542
+ function makeTelegramSendFile(entry2, botToken) {
37188
37543
  return async (filePath, caption) => {
37189
37544
  if (!botToken) {
37190
- console.error(`${TAG62} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
37545
+ console.error(`${TAG62} file-delivery reject reason=no-bot-token sender=${entry2.senderId} role=${entry2.role}`);
37191
37546
  return { ok: false, error: "no-bot-token" };
37192
37547
  }
37193
- if (entry.replyTarget == null) {
37194
- console.error(`${TAG62} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
37548
+ if (entry2.replyTarget == null) {
37549
+ console.error(`${TAG62} file-delivery reject reason=no-reply-target sender=${entry2.senderId} role=${entry2.role}`);
37195
37550
  return { ok: false, error: "no-reply-target" };
37196
37551
  }
37197
37552
  const result = await sendTelegramDocument({
37198
37553
  botToken,
37199
- chatId: Number(entry.replyTarget),
37554
+ chatId: Number(entry2.replyTarget),
37200
37555
  filePath,
37201
37556
  caption,
37202
- maxyAccountId: entry.accountId,
37557
+ maxyAccountId: entry2.accountId,
37203
37558
  platformRoot: platformRoot4()
37204
37559
  });
37205
37560
  return result.ok ? { ok: true } : { ok: false, error: result.error };
37206
37561
  };
37207
37562
  }
37208
- function makeTelegramFileDelivery(entry, botToken) {
37563
+ function makeTelegramFileDelivery(entry2, botToken) {
37209
37564
  return makeFileDelivery({
37210
- entry,
37565
+ entry: entry2,
37211
37566
  tag: TAG62,
37212
37567
  channel: "telegram",
37213
- sendFile: makeTelegramSendFile(entry, botToken)
37568
+ sendFile: makeTelegramSendFile(entry2, botToken)
37214
37569
  });
37215
37570
  }
37216
37571
 
37217
37572
  // app/lib/telegram/gateway/native-file-follower.ts
37218
37573
  function startTelegramNativeFileFollower(input) {
37219
- const entry = {
37574
+ const entry2 = {
37220
37575
  sessionId: input.sessionId,
37221
37576
  role: "admin",
37222
37577
  channel: "telegram",
@@ -37226,7 +37581,6 @@ function startTelegramNativeFileFollower(input) {
37226
37581
  lastInboundAt: Date.now(),
37227
37582
  writeChain: Promise.resolve(),
37228
37583
  subscribers: /* @__PURE__ */ new Set(),
37229
- pendingTurnText: "",
37230
37584
  followerAbort: null,
37231
37585
  followerRunning: false,
37232
37586
  sliceToken: "",
@@ -37235,13 +37589,24 @@ function startTelegramNativeFileFollower(input) {
37235
37589
  // The chat id the document is addressed to (set per spawn from the inbound).
37236
37590
  replyTarget: input.chatId
37237
37591
  };
37238
- return startFollower({
37239
- entry,
37592
+ return startEmitter({
37593
+ entry: entry2,
37240
37594
  tag: "[telegram-adaptor]",
37241
- fileDelivery: makeTelegramFileDelivery(entry, input.botToken),
37595
+ fileDelivery: makeTelegramFileDelivery(entry2, input.botToken),
37242
37596
  // A resumed session's JSONL already holds prior SendUserFile tool_uses;
37243
37597
  // suppress replay so historical files are not re-sent on attach.
37244
37598
  suppressResumeReplay: true,
37599
+ // Task 2521 — the answer reaches every bound door through the one fan-out.
37600
+ // Ungated by design: whatever woke the turn, the reader and the chats show
37601
+ // the same thing.
37602
+ onAnswer: async (answer) => {
37603
+ const targets = input.fanTargets?.() ?? [];
37604
+ if (targets.length === 0) return;
37605
+ noteAnswer(input.sessionId, answer.messageId);
37606
+ const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
37607
+ noteSent(input.sessionId, answer.messageId, reached);
37608
+ console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
37609
+ },
37245
37610
  onClose: input.onClose ?? (() => {
37246
37611
  })
37247
37612
  });
@@ -37269,22 +37634,22 @@ function adminFanTargets(p) {
37269
37634
  noTarget(p.origin, p.accountId, "users-read-failed");
37270
37635
  return [origin];
37271
37636
  }
37272
- const entry = (users ?? []).find((u) => u.userId === p.adminUserId);
37273
- if (!entry) {
37637
+ const entry2 = (users ?? []).find((u) => u.userId === p.adminUserId);
37638
+ if (!entry2) {
37274
37639
  noTarget(p.origin, p.accountId, "no-entry");
37275
37640
  return [origin];
37276
37641
  }
37277
- if (!entry.phone && !entry.telegram) {
37642
+ if (!entry2.phone && !entry2.telegram) {
37278
37643
  noTarget(p.origin, p.accountId, "no-bindings");
37279
37644
  return [origin];
37280
37645
  }
37281
37646
  const bound = [];
37282
- if (entry.phone) {
37283
- const wa = p.deps.whatsappLiveTarget(entry.phone);
37647
+ if (entry2.phone) {
37648
+ const wa = p.deps.whatsappLiveTarget(entry2.phone);
37284
37649
  bound.push(wa ? { channel: "whatsapp", live: true, send: wa } : { channel: "whatsapp", live: false });
37285
37650
  }
37286
- if (entry.telegram) {
37287
- const tg = p.deps.telegramLiveTarget(entry.telegram);
37651
+ if (entry2.telegram) {
37652
+ const tg = p.deps.telegramLiveTarget(entry2.telegram);
37288
37653
  bound.push(tg ? { channel: "telegram", live: true, send: tg } : { channel: "telegram", live: false });
37289
37654
  }
37290
37655
  return [origin, ...bound.filter((t) => t.channel !== p.origin)];
@@ -37491,8 +37856,8 @@ init_resolve_admin_userid();
37491
37856
  init_src2();
37492
37857
  function findAccountManagerDrift(accountManagers, validAccountIds) {
37493
37858
  const drift = [];
37494
- for (const [phone, entry] of Object.entries(accountManagers)) {
37495
- const managesAccount = typeof entry === "string" ? entry : entry.managesAccount;
37859
+ for (const [phone, entry2] of Object.entries(accountManagers)) {
37860
+ const managesAccount = typeof entry2 === "string" ? entry2 : entry2.managesAccount;
37496
37861
  if (!validAccountIds.includes(managesAccount)) {
37497
37862
  drift.push({ phone, managesAccount, reason: "not-in-registry" });
37498
37863
  }
@@ -37607,36 +37972,36 @@ init_sidebar_sessions();
37607
37972
  function buildTelegramAuditLines(input) {
37608
37973
  const lines = [];
37609
37974
  const entries2 = input.accounts.flatMap(
37610
- (a) => listBotEntries(a.telegram).map((entry) => ({ accountId: a.accountId, accountDir: a.accountDir, entry }))
37975
+ (a) => listBotEntries(a.telegram).map((entry2) => ({ accountId: a.accountId, accountDir: a.accountDir, entry: entry2 }))
37611
37976
  );
37612
37977
  if (entries2.length === 0) return lines;
37613
37978
  const byId = /* @__PURE__ */ new Map();
37614
- for (const { entry } of entries2) byId.set(entry.id, (byId.get(entry.id) ?? 0) + 1);
37979
+ for (const { entry: entry2 } of entries2) byId.set(entry2.id, (byId.get(entry2.id) ?? 0) + 1);
37615
37980
  for (const [botId, count] of byId) {
37616
37981
  if (count > 1) lines.push(`[telegram-audit] op=duplicate-bot-id botId=${botId} accounts=${count}`);
37617
37982
  }
37618
- for (const { accountId, accountDir, entry } of entries2) {
37619
- if (entry.role === "public" && !input.agentActive(accountDir, entry.agent)) {
37983
+ for (const { accountId, accountDir, entry: entry2 } of entries2) {
37984
+ if (entry2.role === "public" && !input.agentActive(accountDir, entry2.agent)) {
37620
37985
  lines.push(
37621
- `[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${entry.id} agent=${entry.agent}`
37986
+ `[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${entry2.id} agent=${entry2.agent}`
37622
37987
  );
37623
37988
  }
37624
- if (!input.secretFileExists(entry.id)) {
37625
- lines.push(`[telegram-audit] op=secret-orphan botId=${entry.id} side=entry`);
37989
+ if (!input.secretFileExists(entry2.id)) {
37990
+ lines.push(`[telegram-audit] op=secret-orphan botId=${entry2.id} side=entry`);
37626
37991
  }
37627
37992
  }
37628
37993
  const known = new Set(entries2.map((e) => e.entry.id));
37629
37994
  for (const botId of input.secretFilesOnDisk) {
37630
37995
  if (!known.has(botId)) lines.push(`[telegram-audit] op=secret-orphan botId=${botId} side=file`);
37631
37996
  }
37632
- for (const { accountId, entry } of entries2) {
37633
- const last = input.lastActivityByBot(entry.id);
37997
+ for (const { accountId, entry: entry2 } of entries2) {
37998
+ const last = input.lastActivityByBot(entry2.id);
37634
37999
  const spawnAge = last === null ? "never" : String(input.now - last.spawnMs);
37635
38000
  const turnAge = last === null || last.turnMs === null ? "never" : String(input.now - last.turnMs);
37636
- const agent = entry.role === "admin" ? "admin" : entry.agent;
37637
- const secretFile = input.secretFileExists(entry.id) ? "present" : "absent";
38001
+ const agent = entry2.role === "admin" ? "admin" : entry2.agent;
38002
+ const secretFile = input.secretFileExists(entry2.id) ? "present" : "absent";
37638
38003
  lines.push(
37639
- `[telegram-audit] op=configured botId=${entry.id} accountId=${accountId} agent=${agent} secretFile=${secretFile} lastSpawnAgeMs=${spawnAge} lastTurnAgeMs=${turnAge}`
38004
+ `[telegram-audit] op=configured botId=${entry2.id} accountId=${accountId} agent=${agent} secretFile=${secretFile} lastSpawnAgeMs=${spawnAge} lastTurnAgeMs=${turnAge}`
37640
38005
  );
37641
38006
  }
37642
38007
  return lines;
@@ -37693,7 +38058,7 @@ function* walkTelegramSidecars() {
37693
38058
  }
37694
38059
 
37695
38060
  // server/telegram-migrate.ts
37696
- import { readFileSync as readFileSync51, writeFileSync as writeFileSync19, existsSync as existsSync53, renameSync as renameSync11, mkdirSync as mkdirSync11 } from "fs";
38061
+ import { readFileSync as readFileSync52, writeFileSync as writeFileSync19, existsSync as existsSync52, renameSync as renameSync11, mkdirSync as mkdirSync11 } from "fs";
37697
38062
  import { dirname as dirname18, join as join62 } from "path";
37698
38063
  function migrateTelegramTwoSlot(input) {
37699
38064
  const out = [];
@@ -37701,7 +38066,7 @@ function migrateTelegramTwoSlot(input) {
37701
38066
  const configPath3 = join62(account.accountDir, "account.json");
37702
38067
  let config;
37703
38068
  try {
37704
- config = JSON.parse(readFileSync51(configPath3, "utf-8"));
38069
+ config = JSON.parse(readFileSync52(configPath3, "utf-8"));
37705
38070
  } catch {
37706
38071
  console.error(`[telegram-audit] op=migrate-unreadable accountId=${account.accountId}`);
37707
38072
  continue;
@@ -37754,7 +38119,7 @@ function migrateTelegramTwoSlot(input) {
37754
38119
  return out;
37755
38120
  }
37756
38121
  function moveSecret(from, to) {
37757
- if (!existsSync53(from)) return;
38122
+ if (!existsSync52(from)) return;
37758
38123
  mkdirSync11(dirname18(to), { recursive: true, mode: 448 });
37759
38124
  renameSync11(from, to);
37760
38125
  }
@@ -37803,14 +38168,14 @@ function backfillMisfiledRecords(params) {
37803
38168
  init_message_store();
37804
38169
 
37805
38170
  // server/lib/booking-site-accounts.ts
37806
- import { existsSync as existsSync54, readFileSync as readFileSync52 } from "fs";
37807
- import { resolve as resolve48 } from "path";
38171
+ import { existsSync as existsSync53, readFileSync as readFileSync53 } from "fs";
38172
+ import { resolve as resolve50 } from "path";
37808
38173
  function listBookingSiteAccounts(accounts) {
37809
38174
  return accounts.filter((a) => {
37810
- const availPath = resolve48(a.accountDir, "calendar-availability.json");
37811
- if (!existsSync54(availPath)) return false;
38175
+ const availPath = resolve50(a.accountDir, "calendar-availability.json");
38176
+ if (!existsSync53(availPath)) return false;
37812
38177
  try {
37813
- const cfg = JSON.parse(readFileSync52(availPath, "utf-8"));
38178
+ const cfg = JSON.parse(readFileSync53(availPath, "utf-8"));
37814
38179
  return typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
37815
38180
  } catch {
37816
38181
  return false;
@@ -37832,17 +38197,17 @@ function broadcastAdminShutdown(reason) {
37832
38197
  const done = encoder.encode(`data: [DONE]
37833
38198
 
37834
38199
  `);
37835
- for (const entry of activeAdminSSEControllers) {
38200
+ for (const entry2 of activeAdminSSEControllers) {
37836
38201
  try {
37837
- entry.controller.enqueue(frame);
38202
+ entry2.controller.enqueue(frame);
37838
38203
  } catch {
37839
38204
  }
37840
38205
  try {
37841
- entry.controller.enqueue(done);
38206
+ entry2.controller.enqueue(done);
37842
38207
  } catch {
37843
38208
  }
37844
38209
  try {
37845
- entry.controller.close();
38210
+ entry2.controller.close();
37846
38211
  } catch {
37847
38212
  }
37848
38213
  }
@@ -37855,8 +38220,8 @@ init_paths();
37855
38220
 
37856
38221
  // ../lib/entitlement/src/index.ts
37857
38222
  import { createPublicKey, createHash as createHash7, verify as cryptoVerify } from "crypto";
37858
- import { existsSync as existsSync55, readFileSync as readFileSync53, statSync as statSync29 } from "fs";
37859
- import { resolve as resolve49 } from "path";
38223
+ import { existsSync as existsSync54, readFileSync as readFileSync54, statSync as statSync29 } from "fs";
38224
+ import { resolve as resolve51 } from "path";
37860
38225
 
37861
38226
  // ../lib/entitlement/src/canonicalize.ts
37862
38227
  function canonicalize(value) {
@@ -37891,7 +38256,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
37891
38256
  var GRACE_DAYS = 7;
37892
38257
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
37893
38258
  function pubkeyPath(brand) {
37894
- return resolve49(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
38259
+ return resolve51(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
37895
38260
  }
37896
38261
  var memo = null;
37897
38262
  function memoKey(mtimeMs, account) {
@@ -37903,8 +38268,8 @@ function resolveEntitlement(brand, account) {
37903
38268
  if (brand.commercialMode !== true) {
37904
38269
  return logResolved(implicitTrust(account), null);
37905
38270
  }
37906
- const entitlementPath = resolve49(brand.configDir, "entitlement.json");
37907
- if (!existsSync55(entitlementPath)) {
38271
+ const entitlementPath = resolve51(brand.configDir, "entitlement.json");
38272
+ if (!existsSync54(entitlementPath)) {
37908
38273
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
37909
38274
  }
37910
38275
  const stat11 = statSync29(entitlementPath);
@@ -37919,7 +38284,7 @@ function resolveEntitlement(brand, account) {
37919
38284
  function verifyAndResolve(brand, entitlementPath, account) {
37920
38285
  let pubkeyPem;
37921
38286
  try {
37922
- pubkeyPem = readFileSync53(pubkeyPath(brand), "utf-8");
38287
+ pubkeyPem = readFileSync54(pubkeyPath(brand), "utf-8");
37923
38288
  } catch (err) {
37924
38289
  return logResolved(anonymousFallback("pubkey-missing"), {
37925
38290
  reason: "pubkey-missing"
@@ -37933,7 +38298,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
37933
38298
  }
37934
38299
  let envelope;
37935
38300
  try {
37936
- envelope = JSON.parse(readFileSync53(entitlementPath, "utf-8"));
38301
+ envelope = JSON.parse(readFileSync54(entitlementPath, "utf-8"));
37937
38302
  } catch {
37938
38303
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
37939
38304
  }
@@ -38097,12 +38462,12 @@ installMediaDownloadGuard();
38097
38462
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
38098
38463
  var BRAND_JSON_PATH = PLATFORM_ROOT6 ? join63(PLATFORM_ROOT6, "config", "brand.json") : "";
38099
38464
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
38100
- if (BRAND_JSON_PATH && !existsSync56(BRAND_JSON_PATH)) {
38465
+ if (BRAND_JSON_PATH && !existsSync55(BRAND_JSON_PATH)) {
38101
38466
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
38102
38467
  }
38103
- if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
38468
+ if (BRAND_JSON_PATH && existsSync55(BRAND_JSON_PATH)) {
38104
38469
  try {
38105
- const parsed = JSON.parse(readFileSync54(BRAND_JSON_PATH, "utf-8"));
38470
+ const parsed = JSON.parse(readFileSync55(BRAND_JSON_PATH, "utf-8"));
38106
38471
  BRAND = { ...BRAND, ...parsed };
38107
38472
  } catch (err) {
38108
38473
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -38137,8 +38502,8 @@ var brandLoginOpts = {
38137
38502
  var ALIAS_DOMAINS_PATH = join63(homedir5(), BRAND.configDir, "alias-domains.json");
38138
38503
  function loadAliasDomains() {
38139
38504
  try {
38140
- if (!existsSync56(ALIAS_DOMAINS_PATH)) return null;
38141
- const parsed = JSON.parse(readFileSync54(ALIAS_DOMAINS_PATH, "utf-8"));
38505
+ if (!existsSync55(ALIAS_DOMAINS_PATH)) return null;
38506
+ const parsed = JSON.parse(readFileSync55(ALIAS_DOMAINS_PATH, "utf-8"));
38142
38507
  if (!Array.isArray(parsed)) {
38143
38508
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
38144
38509
  return null;
@@ -38209,6 +38574,26 @@ async function recordPassiveDispatch(input) {
38209
38574
  await session.close();
38210
38575
  }
38211
38576
  }
38577
+ registerLoop({
38578
+ name: "channel-parity",
38579
+ intervalMs: 6e4,
38580
+ run: () => {
38581
+ const lines = parityCensusLines();
38582
+ for (const line of lines) console.error(line);
38583
+ const missing = lines.filter((l) => !l.includes("missing=0")).length;
38584
+ return `${lines.length} session(s), ${missing} with a missing answer`;
38585
+ }
38586
+ });
38587
+ var whatsappFanTargets = (accountId, senderId) => {
38588
+ warnNonUuidAccount("fan-out", accountId);
38589
+ return adminFanTargets({
38590
+ origin: "whatsapp",
38591
+ originKey: senderId,
38592
+ accountId,
38593
+ adminUserId: resolveAdminUserId({ accountId, senderPhone: senderId }),
38594
+ deps: adminFanDeps("whatsapp", accountId)
38595
+ });
38596
+ };
38212
38597
  var waGateway = new WaGateway({
38213
38598
  fetchStandingRules: fetchAccountStandingRules,
38214
38599
  // Task 2321 — the account's own `## Instruction` body, read from that
@@ -38231,15 +38616,11 @@ var waGateway = new WaGateway({
38231
38616
  // Task 2078 — one answer reaches every live door for this admin person.
38232
38617
  // Task 2459 — the origin door is resolved from `senderId`, so it is present
38233
38618
  // even when the sender resolves to no admin at all.
38234
- fanTargetsFor: (accountId, senderId) => adminFanTargets({
38235
- origin: "whatsapp",
38236
- originKey: senderId,
38237
- accountId,
38238
- adminUserId: resolveAdminUserId({ accountId, senderPhone: senderId }),
38239
- deps: adminFanDeps("whatsapp", accountId)
38240
- }),
38619
+ // Task 2521 — hoisted, because the emitter's per-answer fan-out needs the same
38620
+ // resolver and `fanTargetsFor` is a constructor dep, not a callable method.
38621
+ fanTargetsFor: whatsappFanTargets,
38241
38622
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
38242
- serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve50(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
38623
+ serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve52(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
38243
38624
  // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
38244
38625
  // path-validation scope) is the sender's effective SESSION account, resolved
38245
38626
  // once at the inbound gate and threaded here via the gateway's per-sender doc
@@ -38254,7 +38635,7 @@ var waGateway = new WaGateway({
38254
38635
  caption,
38255
38636
  accountId,
38256
38637
  maxyAccountId,
38257
- platformRoot: resolve50(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."))
38638
+ platformRoot: resolve52(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."))
38258
38639
  });
38259
38640
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
38260
38641
  },
@@ -38389,7 +38770,6 @@ var waGateway = new WaGateway({
38389
38770
  console.error(`[whatsapp-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
38390
38771
  return;
38391
38772
  }
38392
- if (role !== "admin") return;
38393
38773
  const prior = nativeFileFollowers.get(senderId);
38394
38774
  if (prior) prior.abort();
38395
38775
  let ac;
@@ -38401,6 +38781,16 @@ var waGateway = new WaGateway({
38401
38781
  // session account (the sub-account for an account-manager), not the house
38402
38782
  // socket account. accountId stays the getSocket key.
38403
38783
  maxyAccountId: effectiveAccountId,
38784
+ // Task 2521 — file delivery is admin-only; the text emitter is not.
38785
+ role,
38786
+ // Task 2521 — resolved per answer, not captured once, so a door that goes
38787
+ // live after this session started still receives.
38788
+ // Task 2531 — `effectiveAccountId`, NOT `accountId`. The reply map is
38789
+ // keyed on the platform UUID (wa-gateway.ts:567) while `accountId` here is
38790
+ // the Baileys credential dirname (see the Task 1850 note above). Passing
38791
+ // the dirname missed every lookup and delivered session 8e5bc4e8's answer
38792
+ // to nothing. `accountId` stays correct on line 718: that is the socket.
38793
+ fanTargets: () => whatsappFanTargets(effectiveAccountId, senderId),
38404
38794
  onClose: () => {
38405
38795
  if (nativeFileFollowers.get(senderId) === ac) nativeFileFollowers.delete(senderId);
38406
38796
  }
@@ -38547,12 +38937,15 @@ var webchatGateway = new WebchatGateway({
38547
38937
  // live admin session (rc-spawn recover:true). Resolve the session id the same
38548
38938
  // way ensureChannelSession does. Public sessions recover via the idle reaper,
38549
38939
  // so a non-admin descriptor is reported not-live (no respawn).
38550
- recoverLiveSession: async ({ accountId, key: key2, role, sessionId, adminUserId }) => {
38940
+ recoverLiveSession: async ({ accountId, key: key2, role, sessionId, adminUserId, subscriberAttached }) => {
38551
38941
  if (role !== "admin") return { live: false };
38552
38942
  const id = sessionId ?? adminSessionIdFor(accountId, key2, adminUserId);
38553
38943
  const r = await managerRcRecover({
38554
38944
  sessionId: id,
38555
38945
  webchatChannel: { key: key2, gatewayUrl: webchatGatewayUrl(), serverPath: webchatServerPath() },
38946
+ // Task 2466 — the gateway resolved this against the live subscriber table
38947
+ // just before the call; the manager classifies from it.
38948
+ subscriberAttached,
38556
38949
  ...adminUserId !== void 0 ? { adminUserId } : {},
38557
38950
  logContext: `channel=webchat key=${key2} recover`
38558
38951
  });
@@ -38573,6 +38966,24 @@ app80.route("/", webchatGateway.routes());
38573
38966
  webchatGateway.startSweeper();
38574
38967
  webchatGateway.startPublicReaper();
38575
38968
  var telegramFileFollowers = /* @__PURE__ */ new Map();
38969
+ var telegramFanTargets = (key2) => {
38970
+ const chatId = key2.slice(key2.indexOf(":") + 1);
38971
+ const account = listValidAccounts().find(
38972
+ (a) => listBotEntries(a.config.telegram).some((e) => e.id === key2.slice(0, key2.indexOf(":")))
38973
+ );
38974
+ if (!account) {
38975
+ console.error(`[channel-fanout] op=no-target origin=telegram key=${key2} accountId=none reason=no-account`);
38976
+ const send = telegramOriginTarget(key2);
38977
+ return [send ? { channel: "telegram", live: true, send } : { channel: "telegram", live: false }];
38978
+ }
38979
+ return adminFanTargets({
38980
+ origin: "telegram",
38981
+ originKey: key2,
38982
+ accountId: account.accountId,
38983
+ adminUserId: resolveTelegramAdminUserId({ accountId: account.accountId, chatId }),
38984
+ deps: adminFanDeps("telegram", account.accountId)
38985
+ });
38986
+ };
38576
38987
  var telegramGateway = new TelegramGateway({
38577
38988
  fetchStandingRules: fetchAccountStandingRules,
38578
38989
  // Task 2353 — no turn-instruction resolver on this door. See the dep comment in
@@ -38582,26 +38993,8 @@ var telegramGateway = new TelegramGateway({
38582
38993
  // is what the classification sidecar records (the manager reads
38583
38994
  // telegramChannel.senderId) and therefore what session-reseat.ts writes.
38584
38995
  resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
38585
- // Task 2078one answer reaches every live door for this admin person. The
38586
- // key is the `<botId>:<chatId>` composite, so the chat id is its tail.
38587
- fanTargetsFor: (key2) => {
38588
- const chatId = key2.slice(key2.indexOf(":") + 1);
38589
- const account = listValidAccounts().find(
38590
- (a) => listBotEntries(a.config.telegram).some((e) => e.id === key2.slice(0, key2.indexOf(":")))
38591
- );
38592
- if (!account) {
38593
- console.error(`[channel-fanout] op=no-target origin=telegram key=${key2} accountId=none reason=no-account`);
38594
- const send = telegramOriginTarget(key2);
38595
- return [send ? { channel: "telegram", live: true, send } : { channel: "telegram", live: false }];
38596
- }
38597
- return adminFanTargets({
38598
- origin: "telegram",
38599
- originKey: key2,
38600
- accountId: account.accountId,
38601
- adminUserId: resolveTelegramAdminUserId({ accountId: account.accountId, chatId }),
38602
- deps: adminFanDeps("telegram", account.accountId)
38603
- });
38604
- },
38996
+ // Task 2521hoisted above; see `telegramFanTargets`.
38997
+ fanTargetsFor: telegramFanTargets,
38605
38998
  gatewayUrl: telegramGatewayUrl(),
38606
38999
  serverPath: telegramServerPath(),
38607
39000
  // Task 2368 — the role branch lives in its own module so it has a test that
@@ -38612,6 +39005,7 @@ var telegramGateway = new TelegramGateway({
38612
39005
  managerSpawn,
38613
39006
  resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
38614
39007
  startFileFollower: startTelegramNativeFileFollower,
39008
+ fanTargets: telegramFanTargets,
38615
39009
  followers: telegramFileFollowers
38616
39010
  })
38617
39011
  });
@@ -38670,9 +39064,9 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
38670
39064
  const entries2 = listBotEntries(a.config.telegram);
38671
39065
  for (const bot of readChannelAdmins(a.accountDir, "telegram").byBot) {
38672
39066
  if (!bot.adminUsers.includes(asNumber)) continue;
38673
- const entry = entries2.find((e) => e.id === bot.botId);
38674
- if (!entry) continue;
38675
- hits.push({ accountId: a.accountId, botId: bot.botId, botToken: entry.token });
39067
+ const entry2 = entries2.find((e) => e.id === bot.botId);
39068
+ if (!entry2) continue;
39069
+ hits.push({ accountId: a.accountId, botId: bot.botId, botToken: entry2.token });
38676
39070
  }
38677
39071
  }
38678
39072
  if (hits.length === 0) return { kind: "none" };
@@ -39052,20 +39446,20 @@ app80.get("/agent-assets/:slug/:filename", (c) => {
39052
39446
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
39053
39447
  return c.text("Not found", 404);
39054
39448
  }
39055
- const filePath = resolve50(account.accountDir, "agents", slug, "assets", filename);
39056
- const expectedDir = resolve50(account.accountDir, "agents", slug, "assets");
39449
+ const filePath = resolve52(account.accountDir, "agents", slug, "assets", filename);
39450
+ const expectedDir = resolve52(account.accountDir, "agents", slug, "assets");
39057
39451
  if (!filePath.startsWith(expectedDir + "/")) {
39058
39452
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
39059
39453
  return c.text("Forbidden", 403);
39060
39454
  }
39061
- if (!existsSync56(filePath)) {
39455
+ if (!existsSync55(filePath)) {
39062
39456
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
39063
39457
  return c.text("Not found", 404);
39064
39458
  }
39065
39459
  const ext = "." + filename.split(".").pop()?.toLowerCase();
39066
39460
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
39067
39461
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
39068
- const body = readFileSync54(filePath);
39462
+ const body = readFileSync55(filePath);
39069
39463
  return c.body(body, 200, {
39070
39464
  "Content-Type": contentType,
39071
39465
  "Cache-Control": "public, max-age=3600"
@@ -39082,20 +39476,20 @@ app80.get("/generated/:filename", (c) => {
39082
39476
  console.error(`[generated] serve file=${filename} status=404`);
39083
39477
  return c.text("Not found", 404);
39084
39478
  }
39085
- const filePath = resolve50(account.accountDir, "generated", filename);
39086
- const expectedDir = resolve50(account.accountDir, "generated");
39479
+ const filePath = resolve52(account.accountDir, "generated", filename);
39480
+ const expectedDir = resolve52(account.accountDir, "generated");
39087
39481
  if (!filePath.startsWith(expectedDir + "/")) {
39088
39482
  console.error(`[generated] serve file=${filename} status=403`);
39089
39483
  return c.text("Forbidden", 403);
39090
39484
  }
39091
- if (!existsSync56(filePath)) {
39485
+ if (!existsSync55(filePath)) {
39092
39486
  console.error(`[generated] serve file=${filename} status=404`);
39093
39487
  return c.text("Not found", 404);
39094
39488
  }
39095
39489
  const ext = "." + filename.split(".").pop()?.toLowerCase();
39096
39490
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
39097
39491
  console.log(`[generated] serve file=${filename} status=200`);
39098
- const body = readFileSync54(filePath);
39492
+ const body = readFileSync55(filePath);
39099
39493
  return c.body(body, 200, {
39100
39494
  "Content-Type": contentType,
39101
39495
  "Cache-Control": "public, max-age=86400"
@@ -39108,9 +39502,9 @@ app80.route("/v", visitor_consent_default);
39108
39502
  var htmlCache = /* @__PURE__ */ new Map();
39109
39503
  var brandLogoPath = "/brand/maxy-monochrome.png";
39110
39504
  var brandIconPath = "/brand/maxy-monochrome.png";
39111
- if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
39505
+ if (BRAND_JSON_PATH && existsSync55(BRAND_JSON_PATH)) {
39112
39506
  try {
39113
- const fullBrand = JSON.parse(readFileSync54(BRAND_JSON_PATH, "utf-8"));
39507
+ const fullBrand = JSON.parse(readFileSync55(BRAND_JSON_PATH, "utf-8"));
39114
39508
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
39115
39509
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
39116
39510
  } catch {
@@ -39137,11 +39531,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
39137
39531
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
39138
39532
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
39139
39533
  var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
39140
- (p) => existsSync56(resolve50(process.cwd(), "public", p.replace(/^\//, "")))
39534
+ (p) => existsSync55(resolve52(process.cwd(), "public", p.replace(/^\//, "")))
39141
39535
  );
39142
39536
  var SW_SOURCE = (() => {
39143
39537
  try {
39144
- return readFileSync54(resolve50(process.cwd(), "public", "sw.js"), "utf-8");
39538
+ return readFileSync55(resolve52(process.cwd(), "public", "sw.js"), "utf-8");
39145
39539
  } catch {
39146
39540
  return null;
39147
39541
  }
@@ -39150,8 +39544,8 @@ function readInstalledVersion() {
39150
39544
  try {
39151
39545
  if (!PLATFORM_ROOT6) return "unknown";
39152
39546
  const versionFile = join63(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
39153
- if (!existsSync56(versionFile)) return "unknown";
39154
- const content = readFileSync54(versionFile, "utf-8").trim();
39547
+ if (!existsSync55(versionFile)) return "unknown";
39548
+ const content = readFileSync55(versionFile, "utf-8").trim();
39155
39549
  return content || "unknown";
39156
39550
  } catch {
39157
39551
  return "unknown";
@@ -39162,7 +39556,7 @@ var versionScript = `<script>window.__MAXY_VERSION__=${JSON.stringify(INSTALLED_
39162
39556
  function cachedHtml(file) {
39163
39557
  let html = htmlCache.get(file);
39164
39558
  if (!html) {
39165
- html = readFileSync54(resolve50(process.cwd(), "public", file), "utf-8");
39559
+ html = readFileSync55(resolve52(process.cwd(), "public", file), "utf-8");
39166
39560
  const productNameEsc = escapeHtml(BRAND.productName);
39167
39561
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
39168
39562
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -39185,8 +39579,8 @@ function loadBrandingCache(agentSlug) {
39185
39579
  const accountId = verdict.kind === "served" ? verdict.owner.accountId : getDefaultAccountId();
39186
39580
  if (!accountId) return null;
39187
39581
  const cachePath = join63(configDir, "branding-cache", accountId, `${agentSlug}.json`);
39188
- if (!existsSync56(cachePath)) return null;
39189
- return JSON.parse(readFileSync54(cachePath, "utf-8"));
39582
+ if (!existsSync55(cachePath)) return null;
39583
+ return JSON.parse(readFileSync55(cachePath, "utf-8"));
39190
39584
  } catch {
39191
39585
  return null;
39192
39586
  }
@@ -39281,7 +39675,7 @@ app80.use("/vnc-popout.html", logViewerFetch);
39281
39675
  app80.get("/vnc-popout.html", (c) => {
39282
39676
  let html = htmlCache.get("vnc-popout.html");
39283
39677
  if (!html) {
39284
- html = readFileSync54(resolve50(process.cwd(), "public", "vnc-popout.html"), "utf-8");
39678
+ html = readFileSync55(resolve52(process.cwd(), "public", "vnc-popout.html"), "utf-8");
39285
39679
  const name = escapeHtml(BRAND.productName);
39286
39680
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
39287
39681
  html = html.replace("</head>", ` ${brandScript}
@@ -39441,9 +39835,9 @@ var httpServer = serve({ fetch: app80.fetch, port, hostname });
39441
39835
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39442
39836
  {
39443
39837
  const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39444
- const reconcileScript = resolve50(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
39838
+ const reconcileScript = resolve52(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
39445
39839
  const runReconcile = (ctx) => {
39446
- if (!existsSync56(reconcileScript)) return;
39840
+ if (!existsSync55(reconcileScript)) return;
39447
39841
  try {
39448
39842
  const child = spawn3(process.execPath, [reconcileScript], {
39449
39843
  env: { ...process.env, PLATFORM_ROOT: reconcilePlatformRoot },
@@ -39482,14 +39876,14 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39482
39876
  {
39483
39877
  const DREAM_CYCLE_SWEEP_INTERVAL_MS = 36e5;
39484
39878
  const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39485
- const dreamSweepScript = resolve50(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
39879
+ const dreamSweepScript = resolve52(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
39486
39880
  registerLoop({
39487
39881
  name: "dream-cycle-sweep",
39488
39882
  intervalMs: DREAM_CYCLE_SWEEP_INTERVAL_MS,
39489
39883
  firstRunDelayMs: 3e4,
39490
39884
  detached: true,
39491
39885
  run: (ctx) => {
39492
- if (!existsSync56(dreamSweepScript)) return;
39886
+ if (!existsSync55(dreamSweepScript)) return;
39493
39887
  try {
39494
39888
  const child = spawn3(process.execPath, [dreamSweepScript], {
39495
39889
  env: { ...process.env, PLATFORM_ROOT: dreamPlatformRoot },
@@ -39506,10 +39900,10 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39506
39900
  }
39507
39901
  {
39508
39902
  const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39509
- const outlookScript = resolve50(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
39903
+ const outlookScript = resolve52(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
39510
39904
  const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
39511
39905
  const runOutlookComplete = (ctx) => {
39512
- if (!existsSync56(outlookScript)) return;
39906
+ if (!existsSync55(outlookScript)) return;
39513
39907
  try {
39514
39908
  const child = spawn3(process.execPath, [outlookScript], {
39515
39909
  env: { ...process.env, PLATFORM_ROOT: outlookPlatformRoot },
@@ -39532,18 +39926,18 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39532
39926
  }
39533
39927
  {
39534
39928
  const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39535
- const strandedAccountsDir = resolve50(auditRoot, "..", "data/accounts");
39929
+ const strandedAccountsDir = resolve52(auditRoot, "..", "data/accounts");
39536
39930
  const STRANDED_AUDIT_INTERVAL_MS = 3e5;
39537
39931
  const STRANDED_AGE_MS = 16 * 6e4;
39538
39932
  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;
39539
39933
  const runStrandedAudit = () => {
39540
39934
  try {
39541
- if (!existsSync56(strandedAccountsDir)) return;
39935
+ if (!existsSync55(strandedAccountsDir)) return;
39542
39936
  const now = Date.now();
39543
39937
  for (const name of readdirSync32(strandedAccountsDir)) {
39544
39938
  if (!STRANDED_UUID_RE.test(name)) continue;
39545
- const pendingPath2 = resolve50(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
39546
- if (!existsSync56(pendingPath2)) continue;
39939
+ const pendingPath2 = resolve52(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
39940
+ if (!existsSync55(pendingPath2)) continue;
39547
39941
  const ageMs2 = now - statSync30(pendingPath2).mtimeMs;
39548
39942
  if (ageMs2 > STRANDED_AGE_MS) {
39549
39943
  console.error(`[outlook-mcp] devicecode-stranded account=${name} ageSec=${Math.floor(ageMs2 / 1e3)}`);
@@ -39562,7 +39956,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39562
39956
  }
39563
39957
  {
39564
39958
  const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39565
- const googleAccountsDir = resolve50(googleRoot, "..", "data/accounts");
39959
+ const googleAccountsDir = resolve52(googleRoot, "..", "data/accounts");
39566
39960
  const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
39567
39961
  const runGooglePendingAuditSafe = () => {
39568
39962
  try {
@@ -39577,10 +39971,10 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39577
39971
  firstRunDelayMs: 27e3,
39578
39972
  run: runGooglePendingAuditSafe
39579
39973
  });
39580
- const googleAuditScript = resolve50(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
39974
+ const googleAuditScript = resolve52(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
39581
39975
  const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
39582
39976
  const runGoogleAccountAudit = (ctx) => {
39583
- if (!existsSync56(googleAuditScript)) return;
39977
+ if (!existsSync55(googleAuditScript)) return;
39584
39978
  try {
39585
39979
  const child = spawn3(process.execPath, [googleAuditScript], {
39586
39980
  env: { ...process.env, PLATFORM_ROOT: googleRoot },
@@ -39640,7 +40034,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39640
40034
  for (const o of migrateTelegramTwoSlot({
39641
40035
  accounts: listValidAccounts().map((a) => ({ accountId: a.accountId, accountDir: a.accountDir })),
39642
40036
  secretPathFor: secretPath,
39643
- legacySecretPathFor: (botType, accountId) => resolve50(
40037
+ legacySecretPathFor: (botType, accountId) => resolve52(
39644
40038
  homedir5(),
39645
40039
  configDirName2(),
39646
40040
  `${botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret"}.${accountId}`
@@ -39669,7 +40063,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39669
40063
  }
39670
40064
  const lines = buildTelegramAuditLines({
39671
40065
  accounts,
39672
- secretFileExists: (botId) => existsSync56(secretPath(botId)),
40066
+ secretFileExists: (botId) => existsSync55(secretPath(botId)),
39673
40067
  agentActive: isActiveAgentSlug,
39674
40068
  secretFilesOnDisk,
39675
40069
  lastActivityByBot: (botId) => lastByBot.get(botId) ?? null,
@@ -39808,7 +40202,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39808
40202
  }
39809
40203
  {
39810
40204
  const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39811
- const publishScript = resolve50(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
40205
+ const publishScript = resolve52(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
39812
40206
  const PUBLISH_INTERVAL_MS = 3e5;
39813
40207
  const bookingAccounts = () => {
39814
40208
  try {
@@ -39819,7 +40213,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39819
40213
  }
39820
40214
  };
39821
40215
  const spawnPublish = (account, ctx) => {
39822
- if (!existsSync56(publishScript)) return;
40216
+ if (!existsSync55(publishScript)) return;
39823
40217
  try {
39824
40218
  const child = spawn3(process.execPath, [publishScript], {
39825
40219
  env: {
@@ -39838,21 +40232,21 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39838
40232
  }
39839
40233
  };
39840
40234
  const auditSnapshotAge = (account) => {
39841
- const availPath = resolve50(account.accountDir, "calendar-availability.json");
40235
+ const availPath = resolve52(account.accountDir, "calendar-availability.json");
39842
40236
  let hasBookingSite = false;
39843
40237
  try {
39844
- if (existsSync56(availPath)) {
39845
- const cfg = JSON.parse(readFileSync54(availPath, "utf-8"));
40238
+ if (existsSync55(availPath)) {
40239
+ const cfg = JSON.parse(readFileSync55(availPath, "utf-8"));
39846
40240
  hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
39847
40241
  }
39848
40242
  } catch {
39849
40243
  }
39850
40244
  if (!hasBookingSite) return;
39851
- const statePath = resolve50(account.accountDir, "state", "booking-availability", "last-publish.json");
40245
+ const statePath = resolve52(account.accountDir, "state", "booking-availability", "last-publish.json");
39852
40246
  let lastSuccessAt = null;
39853
- if (existsSync56(statePath)) {
40247
+ if (existsSync55(statePath)) {
39854
40248
  try {
39855
- const rec = JSON.parse(readFileSync54(statePath, "utf-8"));
40249
+ const rec = JSON.parse(readFileSync55(statePath, "utf-8"));
39856
40250
  lastSuccessAt = rec.lastSuccessAt ?? null;
39857
40251
  } catch {
39858
40252
  console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
@@ -39888,11 +40282,11 @@ startPlannerDivergence();
39888
40282
  startDispatchFieldCensus(() => getSession());
39889
40283
  {
39890
40284
  const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39891
- const auditScript = resolve50(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
40285
+ const auditScript = resolve52(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
39892
40286
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
39893
40287
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
39894
40288
  const runConnectorAudit = (ctx) => {
39895
- if (!existsSync56(auditScript)) return;
40289
+ if (!existsSync55(auditScript)) return;
39896
40290
  try {
39897
40291
  const child = spawn3(process.execPath, [auditScript], {
39898
40292
  env: { ...process.env, PLATFORM_ROOT: auditPlatformRoot, LOG_DIR: auditLogDir },
@@ -40014,11 +40408,11 @@ try {
40014
40408
  var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
40015
40409
  async function runAdminUserReconcileTick() {
40016
40410
  try {
40017
- if (!existsSync56(USERS_FILE)) {
40411
+ if (!existsSync55(USERS_FILE)) {
40018
40412
  console.error("[adminuser-self-heal] skip reason=no-users-file");
40019
40413
  return;
40020
40414
  }
40021
- const usersRaw = readFileSync54(USERS_FILE, "utf-8").trim();
40415
+ const usersRaw = readFileSync55(USERS_FILE, "utf-8").trim();
40022
40416
  if (!usersRaw) {
40023
40417
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
40024
40418
  return;
@@ -40101,7 +40495,7 @@ var runIngestAuditTick = () => {
40101
40495
  agentExists: (accountId, slug) => {
40102
40496
  if (!house) return false;
40103
40497
  const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir : join63(ACCOUNTS_DIR, accountId);
40104
- return existsSync56(join63(dir, "agents", slug, "config.json"));
40498
+ return existsSync55(join63(dir, "agents", slug, "config.json"));
40105
40499
  }
40106
40500
  });
40107
40501
  } catch (err) {
@@ -40150,7 +40544,7 @@ registerLoop({
40150
40544
  houseAccountId: () => resolveAccount()?.accountId ?? null,
40151
40545
  activeSlugs: () => listValidAccounts().map((a) => {
40152
40546
  const agentsRoot = join63(a.accountDir, "agents");
40153
- if (!existsSync56(agentsRoot)) return { accountId: a.accountId, slugs: [] };
40547
+ if (!existsSync55(agentsRoot)) return { accountId: a.accountId, slugs: [] };
40154
40548
  const slugs = readdirSync32(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((slug) => isActiveAgentSlug(a.accountDir, slug));
40155
40549
  return { accountId: a.accountId, slugs };
40156
40550
  })
@@ -40164,8 +40558,8 @@ registerLoop({
40164
40558
  });
40165
40559
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
40166
40560
  function countUsersRows() {
40167
- if (!existsSync56(USERS_FILE)) return 0;
40168
- const raw = readFileSync54(USERS_FILE, "utf-8").trim();
40561
+ if (!existsSync55(USERS_FILE)) return 0;
40562
+ const raw = readFileSync55(USERS_FILE, "utf-8").trim();
40169
40563
  if (!raw) return 0;
40170
40564
  const users = JSON.parse(raw);
40171
40565
  return users.filter((u) => typeof u.userId === "string").length;
@@ -40333,7 +40727,7 @@ if (bootAccountConfig?.whatsapp) {
40333
40727
  }
40334
40728
  init({
40335
40729
  configDir: configDirForWhatsApp,
40336
- platformRoot: resolve50(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..")),
40730
+ platformRoot: resolve52(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..")),
40337
40731
  accountConfig: bootAccountConfig,
40338
40732
  // Task 2074 — enrich with the sub-account name and fan out to every open
40339
40733
  // admin SSE stream. Fire-and-forget: the manager's persist path must not