@rubytech/create-maxy-code 0.1.572 → 0.1.573

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 (306) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +2 -2
  3. package/payload/platform/lib/routine-templates/dist/index.d.ts +2 -2
  4. package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
  5. package/payload/platform/lib/routine-templates/dist/index.js +9 -1
  6. package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
  7. package/payload/platform/lib/routine-templates/src/index.ts +14 -2
  8. package/payload/platform/plugins/admin/mcp/dist/index.js +4 -1
  9. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  10. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +39 -48
  11. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
  12. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +15 -47
  13. package/payload/platform/plugins/docs/references/telegram-guide.md +23 -0
  14. package/payload/platform/plugins/email/PLUGIN.md +1 -1
  15. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js +36 -5
  16. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js.map +1 -1
  17. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +10 -5
  18. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  19. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +12 -6
  20. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  21. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +18 -8
  22. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  23. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.d.ts.map +1 -1
  24. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js +41 -10
  25. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js.map +1 -1
  26. package/payload/platform/plugins/email/references/email-reference.md +12 -0
  27. package/payload/platform/plugins/scheduling/PLUGIN.md +4 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +78 -5
  29. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
  30. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +133 -2
  31. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
  32. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +11 -0
  33. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  34. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +30 -1
  35. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  36. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  37. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +8 -0
  38. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  39. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
  40. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +37 -6
  41. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
  42. package/payload/platform/plugins/telegram/.claude-plugin/plugin.json +1 -1
  43. package/payload/platform/plugins/telegram/PLUGIN.md +14 -2
  44. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.d.ts +2 -0
  45. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.d.ts.map +1 -0
  46. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js +77 -0
  47. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js.map +1 -0
  48. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.d.ts +2 -0
  49. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.d.ts.map +1 -0
  50. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js +49 -0
  51. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js.map +1 -0
  52. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.d.ts +2 -0
  53. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.d.ts.map +1 -0
  54. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.js +25 -0
  55. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.js.map +1 -0
  56. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.d.ts +2 -0
  57. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.d.ts.map +1 -0
  58. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.js +115 -0
  59. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.js.map +1 -0
  60. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.d.ts +2 -0
  61. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.d.ts.map +1 -0
  62. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.js +83 -0
  63. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.js.map +1 -0
  64. package/payload/platform/plugins/telegram/mcp/dist/index.js +42 -0
  65. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  66. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts +32 -0
  67. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts.map +1 -0
  68. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js +59 -0
  69. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js.map +1 -0
  70. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts +18 -0
  71. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts.map +1 -0
  72. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js +35 -0
  73. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js.map +1 -0
  74. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.d.ts +38 -0
  75. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.d.ts.map +1 -0
  76. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.js +21 -0
  77. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.js.map +1 -0
  78. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.d.ts +24 -0
  79. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.d.ts.map +1 -0
  80. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.js +57 -0
  81. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.js.map +1 -0
  82. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.d.ts +16 -0
  83. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.d.ts.map +1 -0
  84. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.js +34 -0
  85. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.js.map +1 -0
  86. package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts +26 -0
  87. package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts.map +1 -0
  88. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js +80 -0
  89. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js.map +1 -0
  90. package/payload/platform/plugins/telegram/skills/build/SKILL.md +110 -0
  91. package/payload/platform/plugins/telegram/skills/build/references/methods.md +219 -0
  92. package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
  93. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  94. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
  95. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  96. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  97. package/payload/platform/services/claude-session-manager/dist/http-server.js +32 -5
  98. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  99. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +12 -1
  100. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  101. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +13 -1
  102. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  103. package/payload/platform/services/telegram-channel/dist/jsonl.d.ts +8 -0
  104. package/payload/platform/services/telegram-channel/dist/jsonl.d.ts.map +1 -0
  105. package/payload/platform/services/telegram-channel/dist/jsonl.js +42 -0
  106. package/payload/platform/services/telegram-channel/dist/jsonl.js.map +1 -0
  107. package/payload/platform/services/telegram-channel/dist/notification.d.ts +0 -31
  108. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  109. package/payload/platform/services/telegram-channel/dist/notification.js +4 -33
  110. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  111. package/payload/platform/services/telegram-channel/dist/server.d.ts +0 -5
  112. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  113. package/payload/platform/services/telegram-channel/dist/server.js +7 -101
  114. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  115. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +0 -66
  116. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  117. package/payload/platform/services/whatsapp-channel/dist/notification.js +2 -64
  118. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  119. package/payload/platform/services/whatsapp-channel/dist/server.d.ts +0 -12
  120. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  121. package/payload/platform/services/whatsapp-channel/dist/server.js +15 -268
  122. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  123. package/payload/server/public/activity.html +6 -6
  124. package/payload/server/public/agents.html +5 -5
  125. package/payload/server/public/assets/{AdminLoginScreens-CMdj1W5p.js → AdminLoginScreens-Dp6k1XgD.js} +1 -1
  126. package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.br +0 -0
  127. package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.gz +0 -0
  128. package/payload/server/public/assets/{AdminShell-CZcaW3BQ.js → AdminShell-D-wB90c3.js} +1 -1
  129. package/payload/server/public/assets/AdminShell-D-wB90c3.js.br +0 -0
  130. package/payload/server/public/assets/AdminShell-D-wB90c3.js.gz +0 -0
  131. package/payload/server/public/assets/{activity-CAhibgb-.js → activity-BJxhblz-.js} +1 -1
  132. package/payload/server/public/assets/activity-BJxhblz-.js.br +0 -0
  133. package/payload/server/public/assets/activity-BJxhblz-.js.gz +0 -0
  134. package/payload/server/public/assets/{admin-xEXv3w9e.js → admin-DzEi8shs.js} +1 -1
  135. package/payload/server/public/assets/admin-DzEi8shs.js.br +0 -0
  136. package/payload/server/public/assets/admin-DzEi8shs.js.gz +0 -0
  137. package/payload/server/public/assets/{agents-CiAmzTaT.js → agents-DTL8kjfD.js} +1 -1
  138. package/payload/server/public/assets/agents-DTL8kjfD.js.br +0 -0
  139. package/payload/server/public/assets/agents-DTL8kjfD.js.gz +0 -0
  140. package/payload/server/public/assets/{browser-D36BiYhS.js → browser-UZp75zhx.js} +1 -1
  141. package/payload/server/public/assets/browser-UZp75zhx.js.br +0 -0
  142. package/payload/server/public/assets/browser-UZp75zhx.js.gz +0 -0
  143. package/payload/server/public/assets/{calendar-CJAM-iXn.js → calendar-Dfo0O2K_.js} +1 -1
  144. package/payload/server/public/assets/calendar-Dfo0O2K_.js.br +0 -0
  145. package/payload/server/public/assets/calendar-Dfo0O2K_.js.gz +0 -0
  146. package/payload/server/public/assets/chat-ByJAvEWZ.js +1 -0
  147. package/payload/server/public/assets/chat-ByJAvEWZ.js.br +0 -0
  148. package/payload/server/public/assets/chat-ByJAvEWZ.js.gz +0 -0
  149. package/payload/server/public/assets/chevron-left-iG7sYNm7.js +1 -0
  150. package/payload/server/public/assets/chevron-left-iG7sYNm7.js.br +0 -0
  151. package/payload/server/public/assets/clock-xSs0UF10.js +1 -0
  152. package/payload/server/public/assets/clock-xSs0UF10.js.br +0 -0
  153. package/payload/server/public/assets/clock-xSs0UF10.js.gz +0 -0
  154. package/payload/server/public/assets/{copy-axBe92iY.js → copy-CGI2jioK.js} +1 -1
  155. package/payload/server/public/assets/copy-CGI2jioK.js.br +0 -0
  156. package/payload/server/public/assets/copy-CGI2jioK.js.gz +0 -0
  157. package/payload/server/public/assets/data-dvwK4BcP.js +1 -0
  158. package/payload/server/public/assets/data-dvwK4BcP.js.br +0 -0
  159. package/payload/server/public/assets/data-dvwK4BcP.js.gz +0 -0
  160. package/payload/server/public/assets/{dispatch-D_i2ko7K.js → dispatch-Dx9-ty1K.js} +1 -1
  161. package/payload/server/public/assets/dispatch-Dx9-ty1K.js.br +0 -0
  162. package/payload/server/public/assets/dispatch-Dx9-ty1K.js.gz +0 -0
  163. package/payload/server/public/assets/{field-DFgkqq1P.js → field-D-P6CsWA.js} +1 -1
  164. package/payload/server/public/assets/field-D-P6CsWA.js.br +0 -0
  165. package/payload/server/public/assets/field-D-P6CsWA.js.gz +0 -0
  166. package/payload/server/public/assets/{file-text-CEFoq0Je.js → file-text-RWintp9q.js} +1 -1
  167. package/payload/server/public/assets/file-text-RWintp9q.js.br +0 -0
  168. package/payload/server/public/assets/file-text-RWintp9q.js.gz +0 -0
  169. package/payload/server/public/assets/{graph-BrpbilNr.js → graph-CpBlfYrJ.js} +1 -1
  170. package/payload/server/public/assets/graph-CpBlfYrJ.js.br +0 -0
  171. package/payload/server/public/assets/graph-CpBlfYrJ.js.gz +0 -0
  172. package/payload/server/public/assets/{graph-labels-BtmcWzo7.js → graph-labels-BOMyPppX.js} +1 -1
  173. package/payload/server/public/assets/graph-labels-BOMyPppX.js.br +0 -0
  174. package/payload/server/public/assets/graph-labels-BOMyPppX.js.gz +0 -0
  175. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css +1 -0
  176. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.br +0 -0
  177. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.gz +0 -0
  178. package/payload/server/public/assets/{operator-Dl7dJt1G.js → operator-DFgW6GiH.js} +1 -1
  179. package/payload/server/public/assets/operator-DFgW6GiH.js.br +0 -0
  180. package/payload/server/public/assets/operator-DFgW6GiH.js.gz +0 -0
  181. package/payload/server/public/assets/{page-nx31Ij0T.js → page-BRShHSH0.js} +1 -1
  182. package/payload/server/public/assets/page-BRShHSH0.js.br +0 -0
  183. package/payload/server/public/assets/page-BRShHSH0.js.gz +0 -0
  184. package/payload/server/public/assets/{page-zC66xzNT.js → page-BbBCu9iP.js} +5 -5
  185. package/payload/server/public/assets/page-BbBCu9iP.js.br +0 -0
  186. package/payload/server/public/assets/page-BbBCu9iP.js.gz +0 -0
  187. package/payload/server/public/assets/play-BJxUaf6-.js +1 -0
  188. package/payload/server/public/assets/play-BJxUaf6-.js.br +0 -0
  189. package/payload/server/public/assets/play-BJxUaf6-.js.gz +0 -0
  190. package/payload/server/public/assets/{public-DEKnH5Q0.js → public-DkObqkzL.js} +1 -1
  191. package/payload/server/public/assets/public-DkObqkzL.js.br +0 -0
  192. package/payload/server/public/assets/public-DkObqkzL.js.gz +0 -0
  193. package/payload/server/public/assets/{rotate-ccw-CBex8gon.js → rotate-ccw-DgMO69nh.js} +1 -1
  194. package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.br +0 -0
  195. package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.gz +0 -0
  196. package/payload/server/public/assets/routines-D9kvg_Xu.js +2 -0
  197. package/payload/server/public/assets/routines-D9kvg_Xu.js.br +0 -0
  198. package/payload/server/public/assets/routines-D9kvg_Xu.js.gz +0 -0
  199. package/payload/server/public/assets/{skills-BmxjPOmq.js → skills-l5I9HZ3D.js} +1 -1
  200. package/payload/server/public/assets/skills-l5I9HZ3D.js.br +0 -0
  201. package/payload/server/public/assets/skills-l5I9HZ3D.js.gz +0 -0
  202. package/payload/server/public/assets/{tasks-BTFYO9vT.js → tasks-C2qJMv6i.js} +1 -1
  203. package/payload/server/public/assets/tasks-C2qJMv6i.js.br +0 -0
  204. package/payload/server/public/assets/tasks-C2qJMv6i.js.gz +0 -0
  205. package/payload/server/public/assets/{triangle-alert-px1YRJJ7.js → triangle-alert-BnC67uSs.js} +1 -1
  206. package/payload/server/public/assets/triangle-alert-BnC67uSs.js.br +0 -0
  207. package/payload/server/public/assets/triangle-alert-BnC67uSs.js.gz +0 -0
  208. package/payload/server/public/assets/{useCopyFeedback-nMXwRcDX.js → useCopyFeedback-1RmgkTFh.js} +1 -1
  209. package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.br +0 -0
  210. package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.gz +0 -0
  211. package/payload/server/public/assets/{useMediaQuery-DjbOTk2X.js → useMediaQuery-BQslddHQ.js} +1 -1
  212. package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.br +0 -0
  213. package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.gz +0 -0
  214. package/payload/server/public/assets/{useVoiceRecorder-Cx7AOmib.js → useVoiceRecorder-BeZNQEC4.js} +1 -1
  215. package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.br +0 -0
  216. package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.gz +0 -0
  217. package/payload/server/public/assets/{wrench-Cexvlx6J.js → wrench-D4ufQG6C.js} +1 -1
  218. package/payload/server/public/assets/wrench-D4ufQG6C.js.br +0 -0
  219. package/payload/server/public/assets/wrench-D4ufQG6C.js.gz +0 -0
  220. package/payload/server/public/browser.html +5 -5
  221. package/payload/server/public/calendar.html +8 -8
  222. package/payload/server/public/chat.html +14 -14
  223. package/payload/server/public/data.html +12 -12
  224. package/payload/server/public/dispatch.html +5 -5
  225. package/payload/server/public/field.html +3 -3
  226. package/payload/server/public/graph.html +10 -10
  227. package/payload/server/public/index.html +14 -14
  228. package/payload/server/public/operator.html +16 -16
  229. package/payload/server/public/public.html +14 -14
  230. package/payload/server/public/routines.html +8 -8
  231. package/payload/server/public/skills.html +6 -6
  232. package/payload/server/public/tasks.html +7 -7
  233. package/payload/server/server.js +739 -449
  234. package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.br +0 -0
  235. package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.gz +0 -0
  236. package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.br +0 -0
  237. package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.gz +0 -0
  238. package/payload/server/public/assets/activity-CAhibgb-.js.br +0 -0
  239. package/payload/server/public/assets/activity-CAhibgb-.js.gz +0 -0
  240. package/payload/server/public/assets/admin-xEXv3w9e.js.br +0 -0
  241. package/payload/server/public/assets/admin-xEXv3w9e.js.gz +0 -0
  242. package/payload/server/public/assets/agents-CiAmzTaT.js.br +0 -0
  243. package/payload/server/public/assets/agents-CiAmzTaT.js.gz +0 -0
  244. package/payload/server/public/assets/browser-D36BiYhS.js.br +0 -0
  245. package/payload/server/public/assets/browser-D36BiYhS.js.gz +0 -0
  246. package/payload/server/public/assets/calendar-CJAM-iXn.js.br +0 -0
  247. package/payload/server/public/assets/calendar-CJAM-iXn.js.gz +0 -0
  248. package/payload/server/public/assets/chat-DhZSvtZK.js +0 -1
  249. package/payload/server/public/assets/chat-DhZSvtZK.js.br +0 -0
  250. package/payload/server/public/assets/chat-DhZSvtZK.js.gz +0 -0
  251. package/payload/server/public/assets/chevron-left-CXEcNlc3.js +0 -1
  252. package/payload/server/public/assets/chevron-left-CXEcNlc3.js.br +0 -0
  253. package/payload/server/public/assets/clock-DdxIJwTr.js +0 -1
  254. package/payload/server/public/assets/clock-DdxIJwTr.js.br +0 -0
  255. package/payload/server/public/assets/clock-DdxIJwTr.js.gz +0 -0
  256. package/payload/server/public/assets/copy-axBe92iY.js.br +0 -0
  257. package/payload/server/public/assets/copy-axBe92iY.js.gz +0 -0
  258. package/payload/server/public/assets/data-DUvkI-zq.js +0 -1
  259. package/payload/server/public/assets/data-DUvkI-zq.js.br +0 -0
  260. package/payload/server/public/assets/data-DUvkI-zq.js.gz +0 -0
  261. package/payload/server/public/assets/dispatch-D_i2ko7K.js.br +0 -0
  262. package/payload/server/public/assets/dispatch-D_i2ko7K.js.gz +0 -0
  263. package/payload/server/public/assets/field-DFgkqq1P.js.br +0 -0
  264. package/payload/server/public/assets/field-DFgkqq1P.js.gz +0 -0
  265. package/payload/server/public/assets/file-text-CEFoq0Je.js.br +0 -0
  266. package/payload/server/public/assets/file-text-CEFoq0Je.js.gz +0 -0
  267. package/payload/server/public/assets/graph-BrpbilNr.js.br +0 -0
  268. package/payload/server/public/assets/graph-BrpbilNr.js.gz +0 -0
  269. package/payload/server/public/assets/graph-labels-BtmcWzo7.js.br +0 -0
  270. package/payload/server/public/assets/graph-labels-BtmcWzo7.js.gz +0 -0
  271. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css +0 -1
  272. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.br +0 -0
  273. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.gz +0 -0
  274. package/payload/server/public/assets/operator-Dl7dJt1G.js.br +0 -3
  275. package/payload/server/public/assets/operator-Dl7dJt1G.js.gz +0 -0
  276. package/payload/server/public/assets/page-nx31Ij0T.js.br +0 -0
  277. package/payload/server/public/assets/page-nx31Ij0T.js.gz +0 -0
  278. package/payload/server/public/assets/page-zC66xzNT.js.br +0 -0
  279. package/payload/server/public/assets/page-zC66xzNT.js.gz +0 -0
  280. package/payload/server/public/assets/play-MbU8O2iC.js +0 -1
  281. package/payload/server/public/assets/play-MbU8O2iC.js.br +0 -0
  282. package/payload/server/public/assets/play-MbU8O2iC.js.gz +0 -0
  283. package/payload/server/public/assets/public-DEKnH5Q0.js.br +0 -0
  284. package/payload/server/public/assets/public-DEKnH5Q0.js.gz +0 -0
  285. package/payload/server/public/assets/rotate-ccw-CBex8gon.js.br +0 -0
  286. package/payload/server/public/assets/rotate-ccw-CBex8gon.js.gz +0 -0
  287. package/payload/server/public/assets/routines-BqCmbvFJ.js +0 -2
  288. package/payload/server/public/assets/routines-BqCmbvFJ.js.br +0 -0
  289. package/payload/server/public/assets/routines-BqCmbvFJ.js.gz +0 -0
  290. package/payload/server/public/assets/skills-BmxjPOmq.js.br +0 -0
  291. package/payload/server/public/assets/skills-BmxjPOmq.js.gz +0 -0
  292. package/payload/server/public/assets/tasks-BTFYO9vT.js.br +0 -0
  293. package/payload/server/public/assets/tasks-BTFYO9vT.js.gz +0 -0
  294. package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.br +0 -0
  295. package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.gz +0 -0
  296. package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.br +0 -0
  297. package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.gz +0 -0
  298. package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.br +0 -0
  299. package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.gz +0 -0
  300. package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.br +0 -0
  301. package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.gz +0 -0
  302. package/payload/server/public/assets/wrench-Cexvlx6J.js.br +0 -0
  303. package/payload/server/public/assets/wrench-Cexvlx6J.js.gz +0 -0
  304. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js → jsx-runtime-5lmxFBIw.js} +0 -0
  305. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.br → jsx-runtime-5lmxFBIw.js.br} +0 -0
  306. /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"}`
@@ -5087,10 +5095,10 @@ var require_dist7 = __commonJS({
5087
5095
 
5088
5096
  // server/index.ts
5089
5097
  init_dist();
5090
- import { readFileSync as readFileSync54, existsSync as existsSync56, watchFile, readdirSync as readdirSync32, statSync as statSync30 } from "fs";
5098
+ import { readFileSync as readFileSync55, existsSync as existsSync56, watchFile, readdirSync as readdirSync32, statSync as statSync30 } from "fs";
5091
5099
  import { spawn as spawn3 } from "child_process";
5092
5100
  import { createHash as createHash8 } from "crypto";
5093
- import { resolve as resolve50, join as join63, basename as basename17 } from "path";
5101
+ import { resolve as resolve51, join as join63, basename as basename17 } from "path";
5094
5102
  import { homedir as homedir5 } from "os";
5095
5103
 
5096
5104
  // server/lib/route-cache-watchers.ts
@@ -6341,12 +6349,12 @@ function getLanIp() {
6341
6349
  let fallback = null;
6342
6350
  for (const entries2 of Object.values(nets)) {
6343
6351
  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;
6352
+ for (const entry2 of entries2) {
6353
+ if (entry2.family !== "IPv4" || entry2.internal) continue;
6354
+ if (entry2.address.startsWith("192.168.") || entry2.address.startsWith("10.")) {
6355
+ return entry2.address;
6348
6356
  }
6349
- if (!fallback) fallback = entry.address;
6357
+ if (!fallback) fallback = entry2.address;
6350
6358
  }
6351
6359
  }
6352
6360
  return fallback;
@@ -6650,20 +6658,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
6650
6658
  // server/routes/health.ts
6651
6659
  init_claude_agent();
6652
6660
  function checkPort(port2, timeoutMs = 500) {
6653
- return new Promise((resolve51) => {
6661
+ return new Promise((resolve52) => {
6654
6662
  const socket = createConnection2(port2, "127.0.0.1");
6655
6663
  socket.setTimeout(timeoutMs);
6656
6664
  socket.once("connect", () => {
6657
6665
  socket.destroy();
6658
- resolve51(true);
6666
+ resolve52(true);
6659
6667
  });
6660
6668
  socket.once("error", () => {
6661
6669
  socket.destroy();
6662
- resolve51(false);
6670
+ resolve52(false);
6663
6671
  });
6664
6672
  socket.once("timeout", () => {
6665
6673
  socket.destroy();
6666
- resolve51(false);
6674
+ resolve52(false);
6667
6675
  });
6668
6676
  });
6669
6677
  }
@@ -7296,12 +7304,12 @@ function readConversationSummaries2(accountId) {
7296
7304
  const seen = /* @__PURE__ */ new Set();
7297
7305
  try {
7298
7306
  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);
7307
+ for (const entry2 of readdirSync2(dir)) {
7308
+ if (!entry2.endsWith(".jsonl")) continue;
7309
+ const stem = entry2.slice(0, -".jsonl".length);
7302
7310
  if (!isStorableStem(stem)) continue;
7303
7311
  const channelKey = fileStemToChannelKey(stem);
7304
- const file = join7(dir, entry);
7312
+ const file = join7(dir, entry2);
7305
7313
  seen.add(file);
7306
7314
  let size;
7307
7315
  let mtimeMs;
@@ -7640,7 +7648,7 @@ async function defaultResolve(accountId) {
7640
7648
  return resolveSocket2(accountId);
7641
7649
  }
7642
7650
  async function fetchAvatar(accountId, jid, deps = {}) {
7643
- const resolve51 = deps.resolve;
7651
+ const resolve52 = deps.resolve;
7644
7652
  const httpGet = deps.httpGet ?? defaultHttpGet;
7645
7653
  const maxAgeMs = deps.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
7646
7654
  const cached3 = readAvatar(accountId, jid);
@@ -7650,7 +7658,7 @@ async function fetchAvatar(accountId, jid, deps = {}) {
7650
7658
  return "fresh";
7651
7659
  }
7652
7660
  console.log(`${TAG7} op=cache jid=${jid} hit=false ageMs=${ageMs2}`);
7653
- const res = resolve51 ? resolve51(accountId) : await defaultResolve(accountId);
7661
+ const res = resolve52 ? resolve52(accountId) : await defaultResolve(accountId);
7654
7662
  if (!res.ok) {
7655
7663
  console.log(`${TAG7} op=no-socket jid=${jid} account=${accountId.slice(0, 8)} reason=${res.reason}`);
7656
7664
  return "no-socket";
@@ -7772,12 +7780,12 @@ async function sweepAvatars() {
7772
7780
  var TAG9 = "[wa-chatmeta]";
7773
7781
  function diffStoredAgainstLive(stored, live) {
7774
7782
  const out = [];
7775
- for (const entry of stored.values()) {
7776
- if (!entry.isGroup) continue;
7777
- const seen = live.get(entry.jid);
7783
+ for (const entry2 of stored.values()) {
7784
+ if (!entry2.isGroup) continue;
7785
+ const seen = live.get(entry2.jid);
7778
7786
  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 });
7787
+ if (seen.name !== entry2.name) {
7788
+ out.push({ jid: entry2.jid, stored: entry2.name, live: seen.name, observedAt: seen.observedAt });
7781
7789
  }
7782
7790
  }
7783
7791
  return out;
@@ -7793,13 +7801,13 @@ async function sweepChatMetadata() {
7793
7801
  const sock = conn.sock;
7794
7802
  const stored = readChatMetadata(conn.platformAccountId);
7795
7803
  const live = /* @__PURE__ */ new Map();
7796
- for (const entry of stored.values()) {
7797
- if (!entry.isGroup) continue;
7804
+ for (const entry2 of stored.values()) {
7805
+ if (!entry2.isGroup) continue;
7798
7806
  groups += 1;
7799
7807
  try {
7800
- const meta = await sock.groupMetadata(entry.jid);
7808
+ const meta = await sock.groupMetadata(entry2.jid);
7801
7809
  if (typeof meta.subject === "string" && meta.subject.length > 0) {
7802
- live.set(entry.jid, { name: meta.subject, observedAt: (/* @__PURE__ */ new Date()).toISOString() });
7810
+ live.set(entry2.jid, { name: meta.subject, observedAt: (/* @__PURE__ */ new Date()).toISOString() });
7803
7811
  checked2 += 1;
7804
7812
  }
7805
7813
  } catch {
@@ -8177,9 +8185,9 @@ function purgeNonSocketAccountManagers(accountDir, socketOwnerId) {
8177
8185
  if (targetId === socketOwnerId) return { removed: [] };
8178
8186
  const map = wa.accountManagers;
8179
8187
  if (!map || typeof map !== "object" || Array.isArray(map)) return { removed: [] };
8180
- const removed = Object.entries(map).map(([phone, entry]) => ({
8188
+ const removed = Object.entries(map).map(([phone, entry2]) => ({
8181
8189
  phone,
8182
- managesAccount: typeof entry === "string" ? entry : entry && typeof entry === "object" && typeof entry.managesAccount === "string" ? entry.managesAccount : ""
8190
+ managesAccount: typeof entry2 === "string" ? entry2 : entry2 && typeof entry2 === "object" && typeof entry2.managesAccount === "string" ? entry2.managesAccount : ""
8183
8191
  }));
8184
8192
  if (removed.length === 0) return { removed: [] };
8185
8193
  delete wa.accountManagers;
@@ -8482,10 +8490,10 @@ function purgeNonSocketRegisteredParties(accountDir, socketOwnerId) {
8482
8490
  if (targetId === socketOwnerId) return { removed: [] };
8483
8491
  const map = wa.registeredParties;
8484
8492
  if (!map || typeof map !== "object" || Array.isArray(map)) return { removed: [] };
8485
- const removed = Object.entries(map).map(([phone, entry]) => ({
8493
+ const removed = Object.entries(map).map(([phone, entry2]) => ({
8486
8494
  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 : ""
8495
+ account: entry2 && typeof entry2 === "object" && typeof entry2.account === "string" ? entry2.account : "",
8496
+ party: entry2 && typeof entry2 === "object" && typeof entry2.party === "string" ? entry2.party : ""
8489
8497
  }));
8490
8498
  if (removed.length === 0) return { removed: [] };
8491
8499
  delete wa.registeredParties;
@@ -9141,8 +9149,8 @@ async function startLogin(opts) {
9141
9149
  await clearAuth(authDir);
9142
9150
  let resolveCode = null;
9143
9151
  let rejectCode = null;
9144
- const codePromise = new Promise((resolve51, reject) => {
9145
- resolveCode = resolve51;
9152
+ const codePromise = new Promise((resolve52, reject) => {
9153
+ resolveCode = resolve52;
9146
9154
  rejectCode = reject;
9147
9155
  });
9148
9156
  const codeTimer = setTimeout(
@@ -9319,27 +9327,27 @@ function bare(value) {
9319
9327
  function reconcilePersonIdentities(rows) {
9320
9328
  const perAccount = /* @__PURE__ */ new Map();
9321
9329
  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);
9330
+ let entry2 = perAccount.get(row.accountId);
9331
+ if (!entry2) {
9332
+ entry2 = { seen: /* @__PURE__ */ new Map(), divergent: 0 };
9333
+ perAccount.set(row.accountId, entry2);
9326
9334
  }
9327
9335
  const p = row.phone ? bare(row.phone) : null;
9328
9336
  const t = row.telephone ? bare(row.telephone) : null;
9329
- if (p && t && p !== t) entry.divergent += 1;
9337
+ if (p && t && p !== t) entry2.divergent += 1;
9330
9338
  const key2 = p ?? t;
9331
- if (key2) entry.seen.set(key2, (entry.seen.get(key2) ?? 0) + 1);
9339
+ if (key2) entry2.seen.set(key2, (entry2.seen.get(key2) ?? 0) + 1);
9332
9340
  }
9333
9341
  const accounts = [];
9334
9342
  let duplicates = 0;
9335
9343
  let divergent = 0;
9336
- for (const [accountId, entry] of perAccount) {
9344
+ for (const [accountId, entry2] of perAccount) {
9337
9345
  let dup = 0;
9338
- for (const count of entry.seen.values()) if (count > 1) dup += count - 1;
9346
+ for (const count of entry2.seen.values()) if (count > 1) dup += count - 1;
9339
9347
  duplicates += dup;
9340
- divergent += entry.divergent;
9341
- if (dup > 0 || entry.divergent > 0) {
9342
- accounts.push({ accountId, duplicates: dup, divergent: entry.divergent });
9348
+ divergent += entry2.divergent;
9349
+ if (dup > 0 || entry2.divergent > 0) {
9350
+ accounts.push({ accountId, duplicates: dup, divergent: entry2.divergent });
9343
9351
  }
9344
9352
  }
9345
9353
  return {
@@ -9362,7 +9370,7 @@ function reconcileSelfPhoneAdmins(input) {
9362
9370
  for (const cred of nonHouse) {
9363
9371
  const selfPhone = cred.selfPhone;
9364
9372
  if (!selfPhone) continue;
9365
- if (input.adminPhones.some((entry) => phonesMatch(entry, selfPhone))) {
9373
+ if (input.adminPhones.some((entry2) => phonesMatch(entry2, selfPhone))) {
9366
9374
  matches.push({ accountId: cred.accountId, phone: selfPhone });
9367
9375
  }
9368
9376
  }
@@ -9771,11 +9779,11 @@ init_src2();
9771
9779
  function buildRegisteredPartyReconcile(params) {
9772
9780
  const rows = [];
9773
9781
  const entries2 = Object.entries(params.registeredParties);
9774
- for (const [phone, entry] of entries2) {
9782
+ for (const [phone, entry2] of entries2) {
9775
9783
  const shadowedBy = isAdminPhone(phone, params.adminPhones) ? "adminPhones" : managedAccountFor(params.accountManagers, phone) ? "accountManagers" : null;
9776
- const unresolved2 = !params.isValidAccount(entry.account);
9784
+ const unresolved2 = !params.isValidAccount(entry2.account);
9777
9785
  if (shadowedBy || unresolved2) {
9778
- rows.push({ phone, account: entry.account, party: entry.party, shadowedBy, unresolved: unresolved2 });
9786
+ rows.push({ phone, account: entry2.account, party: entry2.party, shadowedBy, unresolved: unresolved2 });
9779
9787
  }
9780
9788
  }
9781
9789
  const shadowed = rows.filter((r) => r.shadowedBy !== null).length;
@@ -10185,15 +10193,15 @@ app3.post("/config", async (c) => {
10185
10193
  const agents = [];
10186
10194
  if (existsSync9(agentsDir)) {
10187
10195
  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");
10196
+ for (const entry2 of readdirSync8(agentsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
10197
+ if (!entry2.isDirectory() || entry2.name === "admin") continue;
10198
+ const configPath3 = resolve8(agentsDir, entry2.name, "config.json");
10191
10199
  if (!existsSync9(configPath3)) continue;
10192
10200
  try {
10193
10201
  const parsed = JSON.parse(readFileSync11(configPath3, "utf-8"));
10194
- agents.push({ slug: entry.name, displayName: parsed.displayName ?? entry.name });
10202
+ agents.push({ slug: entry2.name, displayName: parsed.displayName ?? entry2.name });
10195
10203
  } catch {
10196
- console.error(`${TAG16} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
10204
+ console.error(`${TAG16} config action=list-public-agents error="failed to parse config.json for agent ${entry2.name}" \u2014 skipping`);
10197
10205
  }
10198
10206
  }
10199
10207
  } catch (err) {
@@ -10213,8 +10221,8 @@ app3.post("/config", async (c) => {
10213
10221
  const listDenial = bindingScopeDenial(c, callerAccountId, "list-account-managers");
10214
10222
  if (listDenial) return listDenial;
10215
10223
  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));
10224
+ const boundAccountOf = (entry2) => typeof entry2 === "string" ? entry2 : entry2.managesAccount;
10225
+ const accountManagers = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry2]) => boundAccountOf(entry2) === callerAccountId));
10218
10226
  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
10227
  return c.json({ ok: true, accountManagers });
10220
10228
  }
@@ -10252,7 +10260,7 @@ app3.post("/config", async (c) => {
10252
10260
  const listDenial = bindingScopeDenial(c, callerAccountId, "list-registered-parties");
10253
10261
  if (listDenial) return listDenial;
10254
10262
  const all = readRegisteredParties(acct.accountDir);
10255
- const registeredParties = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry]) => entry.account === callerAccountId));
10263
+ const registeredParties = houseAdmin ? all : Object.fromEntries(Object.entries(all).filter(([, entry2]) => entry2.account === callerAccountId));
10256
10264
  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
10265
  return c.json({ ok: true, registeredParties });
10258
10266
  }
@@ -12162,10 +12170,10 @@ function readState(platformRoot5) {
12162
12170
  return {};
12163
12171
  }
12164
12172
  }
12165
- function recordSuccess(platformRoot5, accountId, entry) {
12173
+ function recordSuccess(platformRoot5, accountId, entry2) {
12166
12174
  const path = stateFilePath(platformRoot5);
12167
12175
  const state = readState(platformRoot5);
12168
- state[accountId] = entry;
12176
+ state[accountId] = entry2;
12169
12177
  try {
12170
12178
  writeFileSync6(path, JSON.stringify(state, null, 1));
12171
12179
  } catch (err) {
@@ -12439,10 +12447,10 @@ function readVisitsPushState(platformRoot5) {
12439
12447
  return {};
12440
12448
  }
12441
12449
  }
12442
- function recordVisitsPush(platformRoot5, accountId, entry) {
12450
+ function recordVisitsPush(platformRoot5, accountId, entry2) {
12443
12451
  const path = visitsStateFilePath(platformRoot5);
12444
12452
  const state = readVisitsPushState(platformRoot5);
12445
- state[accountId] = entry;
12453
+ state[accountId] = entry2;
12446
12454
  try {
12447
12455
  writeFileSync7(path, JSON.stringify(state, null, 1));
12448
12456
  } catch (err) {
@@ -13871,8 +13879,8 @@ function listBotEntries(config) {
13871
13879
  function resolveBotEntry(accounts, botId) {
13872
13880
  const hits = [];
13873
13881
  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 });
13882
+ for (const entry2 of listBotEntries(a.config.telegram)) {
13883
+ if (entry2.id === botId) hits.push({ accountId: a.accountId, accountDir: a.accountDir, entry: entry2 });
13876
13884
  }
13877
13885
  }
13878
13886
  if (hits.length === 0) return { kind: "none" };
@@ -14252,6 +14260,12 @@ function pickActivity(entries2, grown, nowMs, concurrencyWindowMs) {
14252
14260
  // server/routes/session-reader.ts
14253
14261
  init_dist();
14254
14262
  var app7 = new Hono();
14263
+ function probeDataRef(path) {
14264
+ const res = resolveDataPath(path);
14265
+ if (!res.ok) {
14266
+ console.error(`[data] op=download-ref-unservable root="data" path="${path}" reason=resolve-failed status=${res.status}`);
14267
+ }
14268
+ }
14255
14269
  function enrichDownloads(turn, scratchpadRoot) {
14256
14270
  if (turn.kind !== "agent-file") return turn;
14257
14271
  let scratchpadReal = null;
@@ -14263,6 +14277,7 @@ function enrichDownloads(turn, scratchpadRoot) {
14263
14277
  const downloads = turn.files.map((f) => {
14264
14278
  const dataRel = relative3(DATA_ROOT, f);
14265
14279
  if (dataRel && !dataRel.startsWith("..") && !isAbsolute2(dataRel)) {
14280
+ probeDataRef(dataRel);
14266
14281
  return { root: "data", path: dataRel };
14267
14282
  }
14268
14283
  if (scratchpadReal) {
@@ -15701,20 +15716,20 @@ function getTelegramGateway() {
15701
15716
 
15702
15717
  // app/lib/telegram/access-control.ts
15703
15718
  function checkTelegramAccess(params) {
15704
- const { senderId, entry } = params;
15705
- if (entry.role === "admin") {
15706
- const adminUsers = entry.adminUsers ?? [];
15719
+ const { senderId, entry: entry2 } = params;
15720
+ if (entry2.role === "admin") {
15721
+ const adminUsers = entry2.adminUsers ?? [];
15707
15722
  if (adminUsers.includes(senderId)) {
15708
15723
  return { allowed: true, reason: "admin-binding", agentType: "admin" };
15709
15724
  }
15710
15725
  return { allowed: false, reason: "not-admin-user", agentType: "admin" };
15711
15726
  }
15712
- const policy = entry.dmPolicy ?? "disabled";
15727
+ const policy = entry2.dmPolicy ?? "disabled";
15713
15728
  switch (policy) {
15714
15729
  case "open":
15715
15730
  return { allowed: true, reason: "dm-policy-open", agentType: "public" };
15716
15731
  case "allowlist": {
15717
- const allowFrom = entry.allowFrom ?? [];
15732
+ const allowFrom = entry2.allowFrom ?? [];
15718
15733
  if (allowFrom.includes(senderId)) {
15719
15734
  return { allowed: true, reason: "allowlist-match", agentType: "public" };
15720
15735
  }
@@ -16224,8 +16239,8 @@ var NOTIFY_MAX = 5e3;
16224
16239
  var cache = /* @__PURE__ */ new Map();
16225
16240
  function isTelegramNotifyDuplicate(notifyId) {
16226
16241
  const now = Date.now();
16227
- const entry = cache.get(notifyId);
16228
- if (entry && now - entry.ts <= NOTIFY_TTL_MS) return true;
16242
+ const entry2 = cache.get(notifyId);
16243
+ if (entry2 && now - entry2.ts <= NOTIFY_TTL_MS) return true;
16229
16244
  for (const [key2, e] of cache) {
16230
16245
  if (now - e.ts > NOTIFY_TTL_MS) cache.delete(key2);
16231
16246
  }
@@ -16324,9 +16339,9 @@ app11.post("/", async (c) => {
16324
16339
  console.error(`${TAG26} op=reject reason=duplicate-bot-id botId=${botId} accounts=${resolved.count}`);
16325
16340
  return c.json({ ok: false }, 401);
16326
16341
  }
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}`);
16342
+ const { accountId, accountDir, entry: entry2 } = resolved;
16343
+ const agentSlug = entry2.role === "admin" ? "admin" : entry2.agent;
16344
+ console.error(`${TAG26} op=entry botId=${botId} accountId=${accountId} role=${entry2.role} agent=${agentSlug}`);
16330
16345
  const sp = secretPath(botId);
16331
16346
  if (!existsSync22(sp)) {
16332
16347
  console.error(`${TAG26} op=secret botId=${botId} result=missing-file`);
@@ -16349,7 +16364,7 @@ app11.post("/", async (c) => {
16349
16364
  console.error(
16350
16365
  `${TAG26} op=inbound-kind botId=${botId} update=${shape.envelope} content=${shape.content} isEdit=${shape.isEdit ? "yes" : "no"}`
16351
16366
  );
16352
- const decision = routeTelegramUpdate({ update, entry });
16367
+ const decision = routeTelegramUpdate({ update, entry: entry2 });
16353
16368
  noteInboundKind(shape.primaryKind, decision.kind === "dispatch");
16354
16369
  if (decision.kind === "ignore") {
16355
16370
  console.error(`${TAG26} op=ignore botId=${botId} reason=${decision.reason}`);
@@ -16369,7 +16384,7 @@ app11.post("/", async (c) => {
16369
16384
  if (decision.allowed) {
16370
16385
  const cbKey = telegramChannelKey(botId, decision.senderId);
16371
16386
  const cbAt = (/* @__PURE__ */ new Date()).toISOString();
16372
- const cbAdminUserId = entry.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: decision.senderId }) : null;
16387
+ const cbAdminUserId = entry2.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: decision.senderId }) : null;
16373
16388
  appendMessage2(accountId, {
16374
16389
  messageId: `telegram:${accountId}:${cbKey}:cb:${decision.callbackId}`,
16375
16390
  sessionId: cbAdminUserId ? adminSessionIdFor(accountId, decision.senderId, cbAdminUserId) : adminSessionIdFor(accountId, decision.senderId, void 0, botId),
@@ -16380,13 +16395,13 @@ app11.post("/", async (c) => {
16380
16395
  senderName: decision.senderDisplay,
16381
16396
  chatId: String(decision.chatId),
16382
16397
  channelKey: cbKey,
16383
- scope: entry.role === "admin" ? "admin" : "public",
16398
+ scope: entry2.role === "admin" ? "admin" : "public",
16384
16399
  origin: "inbound",
16385
16400
  createdAt: cbAt
16386
16401
  });
16387
16402
  }
16388
16403
  const answerStartedAt = Date.now();
16389
- const answered3 = await answerTelegramCallback(entry.token, decision.callbackId);
16404
+ const answered3 = await answerTelegramCallback(entry2.token, decision.callbackId);
16390
16405
  const outcome = !answered3.ok ? "error" : decision.allowed ? "stored" : "refused";
16391
16406
  console.error(
16392
16407
  `${TAG26} op=callback-answered botId=${botId} callbackId=${decision.callbackId} ms=${Date.now() - answerStartedAt} outcome=${outcome}${answered3.error ? ` error=${logValue(answered3.error)}` : ""}`
@@ -16396,8 +16411,8 @@ app11.post("/", async (c) => {
16396
16411
  const { senderId, chatId, text } = decision;
16397
16412
  console.error(`${TAG26} op=access botId=${botId} senderId=${senderId} allowed=true reason=${decision.reason}`);
16398
16413
  const channelKey = telegramChannelKey(botId, senderId);
16399
- const scope = entry.role === "admin" ? "admin" : "public";
16400
- const adminUserId = entry.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: senderId }) : null;
16414
+ const scope = entry2.role === "admin" ? "admin" : "public";
16415
+ const adminUserId = entry2.role === "admin" ? resolveTelegramAdminUserId({ accountId, chatId: senderId }) : null;
16401
16416
  const sessionId = adminUserId ? adminSessionIdFor(accountId, senderId, adminUserId) : adminSessionIdFor(accountId, senderId, void 0, botId);
16402
16417
  const inboundAt = (/* @__PURE__ */ new Date()).toISOString();
16403
16418
  const editSuffix = decision.isEdit ? `:edit:${decision.editDate}` : "";
@@ -16473,7 +16488,7 @@ app11.post("/", async (c) => {
16473
16488
  });
16474
16489
  }
16475
16490
  }
16476
- const active = entry.role === "admin" ? true : isActiveAgentSlug(accountDir, entry.agent);
16491
+ const active = entry2.role === "admin" ? true : isActiveAgentSlug(accountDir, entry2.agent);
16477
16492
  console.error(`${TAG26} op=agent botId=${botId} slug=${agentSlug} active=${active}`);
16478
16493
  if (!active) {
16479
16494
  console.error(`${TAG26} op=reject reason=agent-inactive botId=${botId} agent=${agentSlug}`);
@@ -16485,7 +16500,7 @@ app11.post("/", async (c) => {
16485
16500
  return c.json({ ok: true }, 200);
16486
16501
  }
16487
16502
  const reply = async (replyText) => {
16488
- const sent = await sendTelegramText(entry.token, chatId, replyText);
16503
+ const sent = await sendTelegramText(entry2.token, chatId, replyText);
16489
16504
  if (sent.ok) {
16490
16505
  const at = (/* @__PURE__ */ new Date()).toISOString();
16491
16506
  appendMessage2(accountId, {
@@ -16529,7 +16544,7 @@ app11.post("/", async (c) => {
16529
16544
  });
16530
16545
  if (line) console.error(line);
16531
16546
  }
16532
- const media = await downloadTelegramMedia({ token: entry.token, botId, items: decision.media });
16547
+ const media = await downloadTelegramMedia({ token: entry2.token, botId, items: decision.media });
16533
16548
  void (async () => {
16534
16549
  const heard = await transcribeTelegramAudio({
16535
16550
  botId,
@@ -16541,10 +16556,10 @@ app11.post("/", async (c) => {
16541
16556
  accountId,
16542
16557
  accountDir,
16543
16558
  botId,
16544
- botToken: entry.token,
16559
+ botToken: entry2.token,
16545
16560
  agentSlug,
16546
16561
  senderId,
16547
- role: entry.role,
16562
+ role: entry2.role,
16548
16563
  personId: null,
16549
16564
  // Task 2078 — the admin person this chat is bound to (the users.json
16550
16565
  // telegram binding). It is the SUBJECT of the session id, so one admin's
@@ -16942,9 +16957,9 @@ function computeAdminStoreDivergence(input) {
16942
16957
  result.errors.push({ source: input.accountsDir, detail: err instanceof Error ? err.message : String(err) });
16943
16958
  return result;
16944
16959
  }
16945
- for (const entry of entries2) {
16946
- if (entry.startsWith(".")) continue;
16947
- const accountDir = join35(input.accountsDir, entry);
16960
+ for (const entry2 of entries2) {
16961
+ if (entry2.startsWith(".")) continue;
16962
+ const accountDir = join35(input.accountsDir, entry2);
16948
16963
  try {
16949
16964
  if (!statSync18(accountDir).isDirectory()) continue;
16950
16965
  } catch {
@@ -18987,15 +19002,15 @@ function operatorRoleFor(config, userId) {
18987
19002
  async function buildAccountOptionList(accounts, userId) {
18988
19003
  const houseAccount = accounts.find((a) => a.config.role === "house");
18989
19004
  console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
18990
- const resolve51 = async (a) => {
19005
+ const resolve52 = async (a) => {
18991
19006
  const businessName = await fetchAccountName(a.accountId) || void 0;
18992
19007
  return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
18993
19008
  };
18994
19009
  const houseOptions = await Promise.all(
18995
- accounts.filter((a) => a.config.role === "house").map(resolve51)
19010
+ accounts.filter((a) => a.config.role === "house").map(resolve52)
18996
19011
  );
18997
19012
  const nonHouseOptions = await Promise.all(
18998
- accounts.filter((a) => a.config.role !== "house").map(resolve51)
19013
+ accounts.filter((a) => a.config.role !== "house").map(resolve52)
18999
19014
  );
19000
19015
  const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
19001
19016
  nonHouseOptions.sort((x, y) => {
@@ -19972,7 +19987,7 @@ function authoredAgentFilenames(accountDir) {
19972
19987
  const dir = resolve25(pluginsDir, plugin, "agents");
19973
19988
  if (!existsSync34(dir)) continue;
19974
19989
  try {
19975
- for (const entry of readdirSync22(dir)) if (entry.endsWith(".md")) names.add(entry);
19990
+ for (const entry2 of readdirSync22(dir)) if (entry2.endsWith(".md")) names.add(entry2);
19976
19991
  } catch (err) {
19977
19992
  console.error(`[admin/agents] op=authored-scan read-failed plugin=${plugin} error="${err}"`);
19978
19993
  }
@@ -19998,7 +20013,7 @@ function listShipped(accountDir, riskByTool, disabled, riskSurfaceFailed, author
19998
20013
  for (const dir of dirs) {
19999
20014
  if (!existsSync34(dir)) continue;
20000
20015
  try {
20001
- for (const entry of readdirSync22(dir)) if (entry.endsWith(".md")) names.add(entry);
20016
+ for (const entry2 of readdirSync22(dir)) if (entry2.endsWith(".md")) names.add(entry2);
20002
20017
  } catch (err) {
20003
20018
  console.error(`[admin/agents] op=list-shipped read-failed dir=${dir} error="${err}"`);
20004
20019
  }
@@ -20081,19 +20096,19 @@ function listSpecialists(accountDir, riskByTool, riskSurfaceFailed) {
20081
20096
  } catch {
20082
20097
  continue;
20083
20098
  }
20084
- for (const entry of entries2) {
20085
- if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20099
+ for (const entry2 of entries2) {
20100
+ if (!entry2.isFile() || !entry2.name.endsWith(".md")) continue;
20086
20101
  try {
20087
- const { fm } = splitFrontmatter(readFileSync35(resolve25(agentsDirP, entry.name), "utf-8"));
20102
+ const { fm } = splitFrontmatter(readFileSync35(resolve25(agentsDirP, entry2.name), "utf-8"));
20088
20103
  if (!fm.name) {
20089
20104
  specialistsSkipped++;
20090
- console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry.name}`);
20105
+ console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry2.name}`);
20091
20106
  continue;
20092
20107
  }
20093
20108
  const specialistTools = parseToolsLine(fm.tools);
20094
20109
  const r = classify(specialistTools, riskByTool, riskSurfaceFailed);
20095
20110
  specialists.push({
20096
- slug: entry.name.replace(/\.md$/, ""),
20111
+ slug: entry2.name.replace(/\.md$/, ""),
20097
20112
  displayName: fm.name,
20098
20113
  kind: "specialist",
20099
20114
  origin: "specialist",
@@ -20116,11 +20131,11 @@ function listSpecialists(accountDir, riskByTool, riskSurfaceFailed) {
20116
20131
  // have the surface claim an agent is stopped while it is still being
20117
20132
  // spawned. Absence from the live dir alone is also not the test: a
20118
20133
  // 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))
20134
+ disabled: existsSync34(resolve25(accountDir, ...QUARANTINE_DIR, entry2.name)) && !existsSync34(resolve25(accountDir, ...SPECIALISTS_DIR, entry2.name))
20120
20135
  });
20121
20136
  } catch {
20122
20137
  specialistsSkipped++;
20123
- console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry.name}`);
20138
+ console.error(`[admin/agents] op=list-specialist-skip plugin=${plugin} file=${entry2.name}`);
20124
20139
  }
20125
20140
  }
20126
20141
  }
@@ -20147,16 +20162,16 @@ app23.get("/", requireAdminSession, (c) => {
20147
20162
  if (existsSync34(agentsDir)) {
20148
20163
  try {
20149
20164
  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");
20165
+ for (const entry2 of entries2.sort((a, b) => a.name.localeCompare(b.name))) {
20166
+ if (!entry2.isDirectory()) continue;
20167
+ if (entry2.name === "admin") continue;
20168
+ const configPath3 = resolve25(agentsDir, entry2.name, "config.json");
20154
20169
  if (!existsSync34(configPath3)) continue;
20155
20170
  try {
20156
20171
  const config = JSON.parse(readFileSync35(configPath3, "utf-8"));
20157
20172
  agents.push({
20158
- slug: entry.name,
20159
- displayName: config.displayName ?? entry.name,
20173
+ slug: entry2.name,
20174
+ displayName: config.displayName ?? entry2.name,
20160
20175
  status: config.status ?? "unknown",
20161
20176
  kind: "public",
20162
20177
  origin: "public",
@@ -20169,7 +20184,7 @@ app23.get("/", requireAdminSession, (c) => {
20169
20184
  });
20170
20185
  } catch {
20171
20186
  skipped++;
20172
- console.error(`[admin/agents] failed to parse config.json for agent "${entry.name}" \u2014 skipping`);
20187
+ console.error(`[admin/agents] failed to parse config.json for agent "${entry2.name}" \u2014 skipping`);
20173
20188
  }
20174
20189
  }
20175
20190
  } catch (err) {
@@ -20827,7 +20842,7 @@ app24.get("/", requireAdminSession, async (c) => {
20827
20842
  if (!userId) return c.json({ error: "User identity required \u2014 authenticate with users.json PIN" }, 401);
20828
20843
  try {
20829
20844
  const flushed = await listAdminSessions(accountId, userId, 20);
20830
- const sessions2 = flushed.map((r) => ({
20845
+ const sessions3 = flushed.map((r) => ({
20831
20846
  sessionId: r.sessionId,
20832
20847
  cacheKey: null,
20833
20848
  name: r.name,
@@ -20835,15 +20850,15 @@ app24.get("/", requireAdminSession, async (c) => {
20835
20850
  phase: "flushed",
20836
20851
  channel: r.channel
20837
20852
  })).sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).slice(0, 20);
20838
- const channelCounts = sessions2.reduce((acc, s) => {
20853
+ const channelCounts = sessions3.reduce((acc, s) => {
20839
20854
  const k = s.channel ?? "unknown";
20840
20855
  acc[k] = (acc[k] ?? 0) + 1;
20841
20856
  return acc;
20842
20857
  }, {});
20843
20858
  console.error(
20844
- `[conversations-list] render rows=${sessions2.length} channels=${JSON.stringify(channelCounts)}`
20859
+ `[conversations-list] render rows=${sessions3.length} channels=${JSON.stringify(channelCounts)}`
20845
20860
  );
20846
- return c.json({ sessions: sessions2 });
20861
+ return c.json({ sessions: sessions3 });
20847
20862
  } catch (err) {
20848
20863
  console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
20849
20864
  return c.json({ error: "Failed to fetch sessions" }, 500);
@@ -22539,14 +22554,14 @@ async function walkSubtree(root, dataRoot, out) {
22539
22554
  return { clean: false };
22540
22555
  }
22541
22556
  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()) {
22557
+ for (const entry2 of entries2) {
22558
+ if (entry2.isSymbolicLink()) continue;
22559
+ if (entry2.isDirectory() && entry2.name === ".uploads-tmp") continue;
22560
+ const abs = join43(root, entry2.name);
22561
+ if (entry2.isDirectory()) {
22547
22562
  const sub = await walkSubtree(abs, dataRoot, out);
22548
22563
  if (!sub.clean) clean = false;
22549
- } else if (entry.isFile()) {
22564
+ } else if (entry2.isFile()) {
22550
22565
  try {
22551
22566
  const st = await fsp.stat(abs);
22552
22567
  out.push({
@@ -22838,14 +22853,14 @@ function buildZip(entries2) {
22838
22853
  const localChunks = [];
22839
22854
  const centralChunks = [];
22840
22855
  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);
22856
+ for (const entry2 of entries2) {
22857
+ const nameBytes = Buffer.from(entry2.name, "utf8");
22858
+ const crc = crc32(entry2.data);
22859
+ const uncompressedSize = entry2.data.length;
22860
+ const deflated = deflateRawSync(entry2.data);
22846
22861
  const useDeflate = deflated.length < uncompressedSize;
22847
22862
  const method = useDeflate ? 8 : 0;
22848
- const payload = useDeflate ? deflated : entry.data;
22863
+ const payload = useDeflate ? deflated : entry2.data;
22849
22864
  const compressedSize = payload.length;
22850
22865
  const localHeader = Buffer.alloc(30);
22851
22866
  localHeader.writeUInt32LE(LOCAL_SIG, 0);
@@ -22971,47 +22986,47 @@ async function servableFilesIn(dirAbs, uuid) {
22971
22986
  }
22972
22987
  return out;
22973
22988
  }
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);
22989
+ async function enrich(absolute, entry2, accountNames) {
22990
+ if (entry2.kind === "directory" && UUID_RE3.test(entry2.name)) {
22991
+ const dirAbs = join44(absolute, entry2.name);
22992
+ const meta = await readMeta2(dirAbs, entry2.name);
22978
22993
  if (meta?.filename) {
22979
- const servable = await servableFilesIn(dirAbs, entry.name);
22994
+ const servable = await servableFilesIn(dirAbs, entry2.name);
22980
22995
  if (servable.length === 1) {
22981
22996
  const innerName = servable[0];
22982
22997
  let size = null;
22983
- let modifiedAt = entry.modifiedAt;
22998
+ let modifiedAt = entry2.modifiedAt;
22984
22999
  try {
22985
23000
  const st = await stat6(join44(dirAbs, innerName));
22986
23001
  size = st.size;
22987
23002
  modifiedAt = st.mtime.toISOString();
22988
23003
  } catch {
22989
23004
  }
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}`;
23005
+ entry2.kind = "file";
23006
+ entry2.displayName = meta.filename;
23007
+ entry2.mimeType = meta.mimeType;
23008
+ entry2.sizeBytes = size;
23009
+ entry2.modifiedAt = modifiedAt;
23010
+ entry2.entryPath = `${entry2.name}/${innerName}`;
22996
23011
  return "flattened";
22997
23012
  }
22998
- entry.displayName = meta.filename;
23013
+ entry2.displayName = meta.filename;
22999
23014
  return "kept-as-dir";
23000
23015
  }
23001
- const accountName = accountNames.get(entry.name);
23016
+ const accountName = accountNames.get(entry2.name);
23002
23017
  if (accountName) {
23003
- entry.displayName = accountName;
23018
+ entry2.displayName = accountName;
23004
23019
  }
23005
23020
  return null;
23006
23021
  }
23007
- if (entry.kind === "file") {
23008
- const dot = entry.name.lastIndexOf(".");
23009
- const base = dot === -1 ? entry.name : entry.name.slice(0, dot);
23022
+ if (entry2.kind === "file") {
23023
+ const dot = entry2.name.lastIndexOf(".");
23024
+ const base = dot === -1 ? entry2.name : entry2.name.slice(0, dot);
23010
23025
  if (UUID_RE3.test(base)) {
23011
23026
  const meta = await readMeta2(absolute, base);
23012
23027
  if (meta?.filename) {
23013
- entry.displayName = meta.filename;
23014
- entry.mimeType = meta.mimeType;
23028
+ entry2.displayName = meta.filename;
23029
+ entry2.mimeType = meta.mimeType;
23015
23030
  }
23016
23031
  }
23017
23032
  }
@@ -23102,6 +23117,10 @@ function isValidEntryName(name) {
23102
23117
  if (name.startsWith("@")) return false;
23103
23118
  return !/[\\/\0]/.test(name);
23104
23119
  }
23120
+ function crossAccountOwner(relPath) {
23121
+ const segs = relPath.split("/").filter(Boolean);
23122
+ return segs.length >= 2 && segs[0] === "accounts" ? segs[1] : "";
23123
+ }
23105
23124
  function resolveOwnAccountWrite(rawPath, accountId, endpoint) {
23106
23125
  const res = resolveDataPath(rawPath);
23107
23126
  if (!res.ok) {
@@ -23169,8 +23188,7 @@ app29.get("/", requireAdminSession, async (c) => {
23169
23188
  }
23170
23189
  const { absolute, relative: relPath } = resolution;
23171
23190
  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);
23191
+ 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
23192
  }
23175
23193
  try {
23176
23194
  const info = await stat6(absolute);
@@ -23346,8 +23364,7 @@ app29.get("/download", requireAdminSession, async (c) => {
23346
23364
  return c.json({ error: "Not found" }, 404);
23347
23365
  }
23348
23366
  } 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);
23367
+ 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
23368
  }
23352
23369
  try {
23353
23370
  const info = await stat6(absolute);
@@ -23460,8 +23477,7 @@ app29.get("/download-zip", requireAdminSession, async (c) => {
23460
23477
  }
23461
23478
  const { absolute, relative: relPath } = resolution;
23462
23479
  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);
23480
+ 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
23481
  }
23466
23482
  try {
23467
23483
  const info = await stat6(absolute);
@@ -23469,9 +23485,6 @@ app29.get("/download-zip", requireAdminSession, async (c) => {
23469
23485
  dirsWalked++;
23470
23486
  const parentAbs = dirname15(absolute);
23471
23487
  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
23488
  const fileInfo = await stat6(fileAbs);
23476
23489
  const member = relative6(parentAbs, fileAbs).split(sep9).join("/");
23477
23490
  const over = await addFile(fileAbs, fileInfo.size, member);
@@ -26461,8 +26474,8 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
26461
26474
  if (!existsSync39(dir)) continue;
26462
26475
  try {
26463
26476
  const entries2 = await readdir5(dir);
26464
- for (const entry of entries2) {
26465
- if (entry.endsWith(".md")) names.add(entry);
26477
+ for (const entry2 of entries2) {
26478
+ if (entry2.endsWith(".md")) names.add(entry2);
26466
26479
  }
26467
26480
  } catch (err) {
26468
26481
  const message = err instanceof Error ? err.message : String(err);
@@ -27498,6 +27511,7 @@ async function managerRcRecover(opts) {
27498
27511
  body: JSON.stringify({
27499
27512
  sessionId: opts.sessionId,
27500
27513
  recover: true,
27514
+ subscriberAttached: opts.subscriberAttached,
27501
27515
  role: "admin",
27502
27516
  channel: "webchat",
27503
27517
  senderId: opts.webchatChannel.key,
@@ -28945,6 +28959,8 @@ init_neo4j_store();
28945
28959
  init_src2();
28946
28960
  import cronParser from "cron-parser";
28947
28961
  import { int as int2 } from "neo4j-driver";
28962
+ import { readFileSync as readFileSync44 } from "fs";
28963
+ import { resolve as resolve37 } from "path";
28948
28964
 
28949
28965
  // ../lib/agent-dispatch-rule/src/index.ts
28950
28966
  import { readFileSync as readFileSync43 } from "fs";
@@ -29112,6 +29128,23 @@ var ROUTINE_COLS = `e.eventId AS eventId, e.name AS name, e.eventStatus AS event
29112
29128
  function since24h() {
29113
29129
  return new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
29114
29130
  }
29131
+ function gateClassification(row) {
29132
+ const judged = row.eventStatus === "scheduled" && row.recurrence !== null && row.agentChannel !== null && row.agentDestination !== null && row.agentPrompt !== null;
29133
+ if (!judged) return null;
29134
+ if (row.gatePlugin) return true;
29135
+ return typeof row.gateWaived === "string" && row.gateWaived.trim() !== "";
29136
+ }
29137
+ function gateWatermarkPath(eventId) {
29138
+ return resolve37(PLATFORM_ROOT, "..", "state", "scheduling-gate", `${eventId}.json`);
29139
+ }
29140
+ function readGateLastCommitAt(eventId) {
29141
+ try {
29142
+ const parsed = JSON.parse(readFileSync44(gateWatermarkPath(eventId), "utf-8"));
29143
+ return typeof parsed?.lastCommitAt === "string" ? parsed.lastCommitAt : null;
29144
+ } catch {
29145
+ return null;
29146
+ }
29147
+ }
29115
29148
  function mapRoutine(r) {
29116
29149
  const g = (k) => r.get(k) ?? null;
29117
29150
  const n = (k) => {
@@ -29161,7 +29194,23 @@ function mapRoutine(r) {
29161
29194
  // only the list: the modal's save replaces the list row wholesale
29162
29195
  // (page.tsx:202), so counts served on the list alone would blank on save.
29163
29196
  runs24h: n("runs24h"),
29164
- gated24h: n("gated24h")
29197
+ gated24h: n("gated24h"),
29198
+ // Task 2472 — computed from columns already projected above, so no Cypher
29199
+ // changes and no second query. See gateClassification for why `null` is a
29200
+ // third state rather than a default `true`.
29201
+ gateClassified: gateClassification({
29202
+ eventStatus: g("eventStatus"),
29203
+ recurrence: g("recurrence"),
29204
+ agentChannel: g("agentChannel"),
29205
+ agentDestination: g("agentDestination"),
29206
+ agentPrompt: g("agentPrompt"),
29207
+ gatePlugin: g("gatePlugin"),
29208
+ gateWaived: g("gateWaived")
29209
+ }),
29210
+ // Task 2472 — read only for a gated row: an ungated one has no gate to be
29211
+ // late, and a file read per card on every list would otherwise be spent
29212
+ // proving that.
29213
+ gateLastCommitAt: g("gateTool") === null ? null : readGateLastCommitAt(g("eventId") ?? "")
29165
29214
  };
29166
29215
  }
29167
29216
  function dispatchFieldsOf(r) {
@@ -29919,8 +29968,8 @@ init_dist();
29919
29968
  init_helpers();
29920
29969
  init_account_scope();
29921
29970
  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";
29971
+ import { existsSync as existsSync44, readdirSync as readdirSync26, readFileSync as readFileSync45, rmSync as rmSync6 } from "fs";
29972
+ import { resolve as resolve38 } from "path";
29924
29973
  var app56 = new Hono();
29925
29974
  var NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
29926
29975
  function splitSkillMd(text) {
@@ -29958,18 +30007,18 @@ app56.get("/", requireAdminSession, async (c) => {
29958
30007
  }
29959
30008
  ensureAccountWatchers(acct.accountId, acct.accountDir);
29960
30009
  const { value, state } = await getOrCompute("skills", acct.accountId, async () => {
29961
- const pluginsDir = resolve37(acct.accountDir, "plugins");
30010
+ const pluginsDir = resolve38(acct.accountDir, "plugins");
29962
30011
  const skills = [];
29963
30012
  let pluginCount = 0;
29964
30013
  for (const plugin of subdirs2(pluginsDir)) {
29965
30014
  pluginCount++;
29966
- const skillsDir = resolve37(pluginsDir, plugin, "skills");
30015
+ const skillsDir = resolve38(pluginsDir, plugin, "skills");
29967
30016
  for (const skill of subdirs2(skillsDir)) {
29968
- const skillMd = resolve37(skillsDir, skill, "SKILL.md");
30017
+ const skillMd = resolve38(skillsDir, skill, "SKILL.md");
29969
30018
  if (!existsSync44(skillMd)) continue;
29970
30019
  try {
29971
- const { frontmatter } = splitSkillMd(readFileSync44(skillMd, "utf-8"));
29972
- const refsDir = resolve37(skillsDir, skill, "references");
30020
+ const { frontmatter } = splitSkillMd(readFileSync45(skillMd, "utf-8"));
30021
+ const refsDir = resolve38(skillsDir, skill, "references");
29973
30022
  const refCount = existsSync44(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).length : 0;
29974
30023
  skills.push({ plugin, skill, description: frontmatter.description, refCount });
29975
30024
  } catch (err) {
@@ -29992,14 +30041,14 @@ app56.get("/:plugin/:skill", requireAdminSession, async (c) => {
29992
30041
  console.error(`[admin:skills] op=get outcome=rejected reason=invalid-name plugin="${plugin}" skill="${skill}"`);
29993
30042
  return c.json({ error: "Invalid plugin or skill name" }, 400);
29994
30043
  }
29995
- const skillDir = resolve37(acct.accountDir, "plugins", plugin, "skills", skill);
29996
- const skillMd = resolve37(skillDir, "SKILL.md");
30044
+ const skillDir = resolve38(acct.accountDir, "plugins", plugin, "skills", skill);
30045
+ const skillMd = resolve38(skillDir, "SKILL.md");
29997
30046
  const found = existsSync44(skillMd);
29998
30047
  console.log(`[admin:skills] op=get plugin=${plugin} skill=${skill} found=${found}`);
29999
30048
  if (!found) return c.json({ error: "Skill not found" }, 404);
30000
30049
  try {
30001
- const { frontmatter, body } = splitSkillMd(readFileSync44(skillMd, "utf-8"));
30002
- const refsDir = resolve37(skillDir, "references");
30050
+ const { frontmatter, body } = splitSkillMd(readFileSync45(skillMd, "utf-8"));
30051
+ const refsDir = resolve38(skillDir, "references");
30003
30052
  const references = existsSync44(refsDir) ? readdirSync26(refsDir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name).sort() : [];
30004
30053
  return c.json({ skill: { plugin, skill, frontmatter, body, references } });
30005
30054
  } catch (err) {
@@ -30017,10 +30066,10 @@ app56.delete("/:plugin/:skill", requireAdminSession, async (c) => {
30017
30066
  console.error(`[admin:skills] op=delete outcome=rejected reason=invalid-name plugin="${plugin}" skill="${skill}"`);
30018
30067
  return c.json({ error: "Invalid plugin or skill name" }, 400);
30019
30068
  }
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"))) {
30069
+ const pluginDir = resolve38(acct.accountDir, "plugins", plugin);
30070
+ const skillsDir = resolve38(pluginDir, "skills");
30071
+ const skillDir = resolve38(skillsDir, skill);
30072
+ if (!existsSync44(resolve38(skillDir, "SKILL.md"))) {
30024
30073
  console.error(`[admin:skills] op=delete outcome=rejected reason=not-found plugin=${plugin} skill=${skill}`);
30025
30074
  return c.json({ error: "Skill not found" }, 404);
30026
30075
  }
@@ -30672,8 +30721,8 @@ init_neo4j_store();
30672
30721
  // app/lib/admin-roster.ts
30673
30722
  init_account();
30674
30723
  init_neo4j_store();
30675
- import { existsSync as existsSync45, readdirSync as readdirSync27, readFileSync as readFileSync45 } from "fs";
30676
- import { resolve as resolve38 } from "path";
30724
+ import { existsSync as existsSync45, readdirSync as readdirSync27, readFileSync as readFileSync46 } from "fs";
30725
+ import { resolve as resolve39 } from "path";
30677
30726
  function readHouseAdminUserIds(accountsDir = ACCOUNTS_DIR) {
30678
30727
  let entries2;
30679
30728
  try {
@@ -30681,11 +30730,11 @@ function readHouseAdminUserIds(accountsDir = ACCOUNTS_DIR) {
30681
30730
  } catch {
30682
30731
  return [];
30683
30732
  }
30684
- for (const entry of entries2) {
30685
- const cfgPath = resolve38(accountsDir, entry, "account.json");
30733
+ for (const entry2 of entries2) {
30734
+ const cfgPath = resolve39(accountsDir, entry2, "account.json");
30686
30735
  if (!existsSync45(cfgPath)) continue;
30687
30736
  try {
30688
- const cfg = JSON.parse(readFileSync45(cfgPath, "utf-8"));
30737
+ const cfg = JSON.parse(readFileSync46(cfgPath, "utf-8"));
30689
30738
  if (cfg.role !== "house") continue;
30690
30739
  if (!Array.isArray(cfg.admins)) return [];
30691
30740
  return cfg.admins.map(
@@ -31033,7 +31082,7 @@ init_claude_agent();
31033
31082
  init_account();
31034
31083
  init_neo4j_store();
31035
31084
  init_src2();
31036
- import { resolve as resolve39 } from "path";
31085
+ import { resolve as resolve40 } from "path";
31037
31086
  var TAG49 = "[admin:routines]";
31038
31087
  function scopeFor(d, houseDir, houseAccountId) {
31039
31088
  if (d.channel !== "whatsapp") return null;
@@ -31063,7 +31112,7 @@ app67.get("/", requireAdminSession, async (c) => {
31063
31112
  console.error(`${TAG49} op=destinations account=${accountId.slice(0, 8)} outcome=house-unresolved reason="${message}"`);
31064
31113
  }
31065
31114
  const destinations = listDispatchDestinations({ accountsDir: ACCOUNTS_DIR, houseAccountId, accountId });
31066
- const houseDir = houseAccountId === null ? null : resolve39(ACCOUNTS_DIR, houseAccountId);
31115
+ const houseDir = houseAccountId === null ? null : resolve40(ACCOUNTS_DIR, houseAccountId);
31067
31116
  const byScope = /* @__PURE__ */ new Map();
31068
31117
  for (const d of destinations) {
31069
31118
  const scope = houseDir === null || houseAccountId === null ? null : scopeFor(d, houseDir, houseAccountId);
@@ -31413,9 +31462,9 @@ init_dist();
31413
31462
 
31414
31463
  // app/lib/access-email.ts
31415
31464
  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(
31465
+ import { resolve as resolve41 } from "path";
31466
+ var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve41(process.cwd(), "..");
31467
+ var SEND_SCRIPT = resolve41(
31419
31468
  PLATFORM_ROOT5,
31420
31469
  "plugins",
31421
31470
  "email",
@@ -31559,8 +31608,8 @@ var access_default = app72;
31559
31608
  // server/routes/sites.ts
31560
31609
  init_dist();
31561
31610
  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";
31611
+ import { existsSync as existsSync46, readFileSync as readFileSync47, realpathSync as realpathSync8, statSync as statSync25 } from "fs";
31612
+ import { resolve as resolve42 } from "path";
31564
31613
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
31565
31614
  var MIME = {
31566
31615
  ".html": "text/html; charset=utf-8",
@@ -31617,8 +31666,8 @@ app73.get("/:rel{.*}", (c) => {
31617
31666
  }
31618
31667
  segments.push(seg);
31619
31668
  }
31620
- const rootDir = resolve41(account.accountDir, "sites");
31621
- let filePath = segments.length === 0 ? rootDir : resolve41(rootDir, ...segments);
31669
+ const rootDir = resolve42(account.accountDir, "sites");
31670
+ let filePath = segments.length === 0 ? rootDir : resolve42(rootDir, ...segments);
31622
31671
  if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
31623
31672
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
31624
31673
  return c.text("Forbidden", 403);
@@ -31638,7 +31687,7 @@ app73.get("/:rel{.*}", (c) => {
31638
31687
  return c.redirect(target, 301);
31639
31688
  }
31640
31689
  if (stat11?.isDirectory()) {
31641
- filePath = resolve41(filePath, "index.html");
31690
+ filePath = resolve42(filePath, "index.html");
31642
31691
  }
31643
31692
  if (!filePath.startsWith(rootDir + "/")) {
31644
31693
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
@@ -31663,7 +31712,7 @@ app73.get("/:rel{.*}", (c) => {
31663
31712
  }
31664
31713
  let body;
31665
31714
  try {
31666
- body = readFileSync46(realPath);
31715
+ body = readFileSync47(realPath);
31667
31716
  } catch (err) {
31668
31717
  const code = err?.code;
31669
31718
  if (code === "EISDIR") {
@@ -31705,7 +31754,7 @@ init_neo4j_store();
31705
31754
  // app/lib/visitor-token.ts
31706
31755
  init_paths();
31707
31756
  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";
31757
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync48, writeFileSync as writeFileSync17 } from "fs";
31709
31758
  import { dirname as dirname16 } from "path";
31710
31759
  var TOKEN_PREFIX = "v1.";
31711
31760
  var SECRET_BYTES = 32;
@@ -31714,7 +31763,7 @@ var cachedSecret = null;
31714
31763
  function getSecret() {
31715
31764
  if (cachedSecret) return cachedSecret;
31716
31765
  try {
31717
- const hex2 = readFileSync47(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
31766
+ const hex2 = readFileSync48(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
31718
31767
  if (hex2.length === SECRET_BYTES * 2) {
31719
31768
  cachedSecret = Buffer.from(hex2, "hex");
31720
31769
  return cachedSecret;
@@ -31728,7 +31777,7 @@ function getSecret() {
31728
31777
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
31729
31778
  } catch {
31730
31779
  }
31731
- const hex = readFileSync47(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
31780
+ const hex = readFileSync48(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
31732
31781
  cachedSecret = Buffer.from(hex, "hex");
31733
31782
  return cachedSecret;
31734
31783
  }
@@ -31784,7 +31833,7 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
31784
31833
  init_dist();
31785
31834
 
31786
31835
  // app/lib/brand-config.ts
31787
- import { existsSync as existsSync47, readFileSync as readFileSync48 } from "fs";
31836
+ import { existsSync as existsSync47, readFileSync as readFileSync49 } from "fs";
31788
31837
  import { join as join53 } from "path";
31789
31838
  var cached2 = null;
31790
31839
  var cachedAttempted = false;
@@ -31796,7 +31845,7 @@ function readBrandConfig() {
31796
31845
  const brandPath = join53(platformRoot5, "config", "brand.json");
31797
31846
  if (!existsSync47(brandPath)) return null;
31798
31847
  try {
31799
- cached2 = JSON.parse(readFileSync48(brandPath, "utf-8"));
31848
+ cached2 = JSON.parse(readFileSync49(brandPath, "utf-8"));
31800
31849
  return cached2;
31801
31850
  } catch {
31802
31851
  return null;
@@ -32075,9 +32124,9 @@ function originAllowed(origin, allowlist) {
32075
32124
  try {
32076
32125
  const u = new URL(origin);
32077
32126
  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));
32127
+ return allowlist.some((entry2) => {
32128
+ if (entry2 === host) return true;
32129
+ if (entry2.startsWith("*.")) return host.endsWith(entry2.slice(1));
32081
32130
  return false;
32082
32131
  });
32083
32132
  } catch {
@@ -32299,16 +32348,16 @@ var visitor_event_default = app76;
32299
32348
  // server/routes/session.ts
32300
32349
  init_dist();
32301
32350
  init_claude_agent();
32302
- import { resolve as resolve42 } from "path";
32351
+ import { resolve as resolve43 } from "path";
32303
32352
  import { existsSync as existsSync48, writeFileSync as writeFileSync18, mkdirSync as mkdirSync10 } from "fs";
32304
32353
  init_neo4j_store();
32305
32354
  init_paths();
32306
32355
  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
32356
  function writeBrandingCache(accountId, agentSlug, branding) {
32308
32357
  try {
32309
- const cacheDir = resolve42(MAXY_DIR, "branding-cache", accountId);
32358
+ const cacheDir = resolve43(MAXY_DIR, "branding-cache", accountId);
32310
32359
  mkdirSync10(cacheDir, { recursive: true });
32311
- writeFileSync18(resolve42(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
32360
+ writeFileSync18(resolve43(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
32312
32361
  } catch (err) {
32313
32362
  console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
32314
32363
  }
@@ -32389,8 +32438,8 @@ app77.post("/", async (c) => {
32389
32438
  }
32390
32439
  let agentConfig = null;
32391
32440
  if (account) {
32392
- const agentDir = resolve42(account.accountDir, "agents", agentSlug);
32393
- if (!existsSync48(agentDir) || !existsSync48(resolve42(agentDir, "config.json"))) {
32441
+ const agentDir = resolve43(account.accountDir, "agents", agentSlug);
32442
+ if (!existsSync48(agentDir) || !existsSync48(resolve43(agentDir, "config.json"))) {
32394
32443
  return c.json({ error: "Agent not found" }, 404);
32395
32444
  }
32396
32445
  agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
@@ -32574,7 +32623,7 @@ var calendar_public_default = app78;
32574
32623
  init_dist();
32575
32624
  import { createReadStream as createReadStream3 } from "fs";
32576
32625
  import { stat as stat8, readFile as readFile11, realpath } from "fs/promises";
32577
- import { join as join54, resolve as resolve43, sep as sep11 } from "path";
32626
+ import { join as join54, resolve as resolve44, sep as sep11 } from "path";
32578
32627
  import { Readable as Readable3 } from "stream";
32579
32628
  init_data_path();
32580
32629
  init_attachments();
@@ -32629,7 +32678,7 @@ ${expiresAtMs}`)
32629
32678
  return diff2 === 0;
32630
32679
  }
32631
32680
  async function readAccountSecret(accountId) {
32632
- const path = resolve43(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
32681
+ const path = resolve44(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
32633
32682
  let text;
32634
32683
  try {
32635
32684
  text = await readFile11(path, "utf8");
@@ -32665,7 +32714,7 @@ async function resolveFetchTarget(accountDir, relPath, ownerId) {
32665
32714
  const exposeFolders = await readExposeFolders2(accountDir);
32666
32715
  const { exposed } = resolveExposedDirs(schemaText, exposeFolders);
32667
32716
  if (exposed.length === 0 && !ownerId) return { ok: false, reason: "not-exposed" };
32668
- const lexical = resolve43(accountDir, relPath);
32717
+ const lexical = resolve44(accountDir, relPath);
32669
32718
  let root;
32670
32719
  let absolute;
32671
32720
  try {
@@ -32714,7 +32763,7 @@ async function handle(c, headOnly) {
32714
32763
  return c.json({ ok: false, error: "denied" }, 401);
32715
32764
  }
32716
32765
  const target = await resolveFetchTarget(
32717
- resolve43(DATA_ROOT, "accounts", accountId),
32766
+ resolve44(DATA_ROOT, "accounts", accountId),
32718
32767
  relPath,
32719
32768
  owner || void 0
32720
32769
  );
@@ -32836,7 +32885,7 @@ init_neo4j_store();
32836
32885
 
32837
32886
  // app/lib/ledger-census.ts
32838
32887
  init_loop_registry();
32839
- import { readdirSync as readdirSync29, readFileSync as readFileSync49, statSync as statSync26 } from "fs";
32888
+ import { readdirSync as readdirSync29, readFileSync as readFileSync50, statSync as statSync26 } from "fs";
32840
32889
  import { join as join55 } from "path";
32841
32890
 
32842
32891
  // ../lib/ledger-core/dist/reconcile.js
@@ -32894,7 +32943,7 @@ function countWriteRejects24h(logDir, now) {
32894
32943
  const path = join55(logDir, name);
32895
32944
  try {
32896
32945
  if (now - statSync26(path).mtimeMs > DAY_MS) continue;
32897
- for (const line of readFileSync49(path, "utf8").split("\n")) {
32946
+ for (const line of readFileSync50(path, "utf8").split("\n")) {
32898
32947
  if (line.includes("[graph-write] reject")) total += 1;
32899
32948
  }
32900
32949
  } catch {
@@ -32997,7 +33046,7 @@ function startLedgerCensus(openSession, opts = {}) {
32997
33046
  // app/lib/dispatch-census.ts
32998
33047
  init_loop_registry();
32999
33048
  init_account();
33000
- import { readFileSync as readFileSync50 } from "fs";
33049
+ import { readFileSync as readFileSync51 } from "fs";
33001
33050
  import { join as join56 } from "path";
33002
33051
  var CENSUS_TAG2 = "[dispatch-census]";
33003
33052
  var CONFIG_AUDIT_TAG = "[dispatch-config-audit]";
@@ -33031,7 +33080,7 @@ function percentile(sortedAsc, p) {
33031
33080
  function auditConfigFile(accountDir) {
33032
33081
  let cfg;
33033
33082
  try {
33034
- cfg = JSON.parse(readFileSync50(join56(accountDir, "dispatch", "config.json"), "utf-8"));
33083
+ cfg = JSON.parse(readFileSync51(join56(accountDir, "dispatch", "config.json"), "utf-8"));
33035
33084
  } catch {
33036
33085
  return null;
33037
33086
  }
@@ -33056,7 +33105,7 @@ function auditConfigFile(accountDir) {
33056
33105
  function terminalStatuses(accountDir) {
33057
33106
  try {
33058
33107
  const cfg = JSON.parse(
33059
- readFileSync50(join56(accountDir, "dispatch", "config.json"), "utf-8")
33108
+ readFileSync51(join56(accountDir, "dispatch", "config.json"), "utf-8")
33060
33109
  );
33061
33110
  return {
33062
33111
  job: (cfg.statuses?.job ?? []).filter((s) => s.terminal).map((s) => s.name),
@@ -33493,7 +33542,7 @@ function startSharedFolderCensus() {
33493
33542
  // app/lib/file-watcher.ts
33494
33543
  init_data_path();
33495
33544
  import * as fsp2 from "fs/promises";
33496
- import { resolve as resolve44, sep as sep12 } from "path";
33545
+ import { resolve as resolve45, sep as sep12 } from "path";
33497
33546
  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
33547
  var DEFAULT_COALESCE_MS = 500;
33499
33548
  var ROOTS = ["accounts"];
@@ -33512,7 +33561,7 @@ async function startFileWatcher(opts = {}) {
33512
33561
  const dropFn = opts.drop ?? dropFileIndex;
33513
33562
  for (const r of ROOTS) {
33514
33563
  try {
33515
- await fsp2.mkdir(resolve44(dataRoot, r), { recursive: true });
33564
+ await fsp2.mkdir(resolve45(dataRoot, r), { recursive: true });
33516
33565
  } catch (err) {
33517
33566
  console.error(
33518
33567
  `[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
@@ -33533,7 +33582,7 @@ async function startFileWatcher(opts = {}) {
33533
33582
  timers.set(relativePath, t);
33534
33583
  }
33535
33584
  async function runHook(relativePath, accountId) {
33536
- const absolute = resolve44(dataRoot, relativePath);
33585
+ const absolute = resolve45(dataRoot, relativePath);
33537
33586
  let exists = false;
33538
33587
  try {
33539
33588
  const st = await fsp2.stat(absolute);
@@ -33557,7 +33606,7 @@ async function startFileWatcher(opts = {}) {
33557
33606
  }
33558
33607
  }
33559
33608
  async function watchRoot(rootName) {
33560
- const absRoot = resolve44(dataRoot, rootName);
33609
+ const absRoot = resolve45(dataRoot, rootName);
33561
33610
  try {
33562
33611
  const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
33563
33612
  for await (const event of iter) {
@@ -33599,7 +33648,7 @@ init_data_path();
33599
33648
  init_neo4j_store();
33600
33649
  init_account();
33601
33650
  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";
33651
+ import { dirname as dirname17, relative as relative8, resolve as resolve46 } from "path";
33603
33652
  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
33653
  async function walkFiles(dir) {
33605
33654
  const out = [];
@@ -33610,7 +33659,7 @@ async function walkFiles(dir) {
33610
33659
  return out;
33611
33660
  }
33612
33661
  for (const e of entries2) {
33613
- const abs = resolve45(dir, e.name);
33662
+ const abs = resolve46(dir, e.name);
33614
33663
  if (e.isDirectory()) {
33615
33664
  out.push(...await walkFiles(abs));
33616
33665
  } else if (e.isFile()) {
@@ -33630,7 +33679,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
33630
33679
  let moved = 0;
33631
33680
  for (const oldAbs of files) {
33632
33681
  const suffix = relative8(srcDir, oldAbs);
33633
- const newAbs = resolve45(destDir, suffix);
33682
+ const newAbs = resolve46(destDir, suffix);
33634
33683
  await mkdir5(dirname17(newAbs), { recursive: true });
33635
33684
  await rename4(oldAbs, newAbs);
33636
33685
  moved++;
@@ -33657,7 +33706,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
33657
33706
  }
33658
33707
  async function migrateUploads(opts = {}) {
33659
33708
  const dataRoot = opts.dataRoot ?? DATA_ROOT;
33660
- const oldRoot = resolve45(dataRoot, "uploads");
33709
+ const oldRoot = resolve46(dataRoot, "uploads");
33661
33710
  let topEntries;
33662
33711
  try {
33663
33712
  topEntries = await readdir6(oldRoot, { withFileTypes: true });
@@ -33674,12 +33723,12 @@ async function migrateUploads(opts = {}) {
33674
33723
  let moved = 0;
33675
33724
  let skipped = 0;
33676
33725
  try {
33677
- for (const entry of topEntries) {
33678
- const name = entry.name;
33726
+ for (const entry2 of topEntries) {
33727
+ const name = entry2.name;
33679
33728
  if (ACCOUNT_UUID_RE4.test(name)) {
33680
33729
  moved += await relocateTree(
33681
- resolve45(oldRoot, name),
33682
- resolve45(dataRoot, "accounts", name, "uploads"),
33730
+ resolve46(oldRoot, name),
33731
+ resolve46(dataRoot, "accounts", name, "uploads"),
33683
33732
  dataRoot,
33684
33733
  name,
33685
33734
  session
@@ -33691,8 +33740,8 @@ async function migrateUploads(opts = {}) {
33691
33740
  continue;
33692
33741
  }
33693
33742
  moved += await relocateTree(
33694
- resolve45(oldRoot, "public"),
33695
- resolve45(dataRoot, "accounts", installAccountId, "uploads", "public"),
33743
+ resolve46(oldRoot, "public"),
33744
+ resolve46(dataRoot, "accounts", installAccountId, "uploads", "public"),
33696
33745
  dataRoot,
33697
33746
  null,
33698
33747
  // public uploads carry no graph nodes
@@ -33779,11 +33828,11 @@ async function collectLiveSidecars(projectsRoot) {
33779
33828
  } catch {
33780
33829
  continue;
33781
33830
  }
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);
33831
+ for (const entry2 of entries2) {
33832
+ if (entry2.isFile() && SESSION_META_RE.test(entry2.name)) {
33833
+ out.push(join58(slugDir, entry2.name));
33834
+ } else if (entry2.isDirectory() && entry2.name === "subagents") {
33835
+ const subDir = join58(slugDir, entry2.name);
33787
33836
  let subs;
33788
33837
  try {
33789
33838
  subs = await readdir7(subDir, { withFileTypes: true });
@@ -33934,11 +33983,11 @@ async function collectSidecars(projectsRoot) {
33934
33983
  } catch {
33935
33984
  continue;
33936
33985
  }
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);
33986
+ for (const entry2 of entries2) {
33987
+ if (entry2.isFile() && SESSION_META_RE2.test(entry2.name)) {
33988
+ out.push({ path: join59(slugDir, entry2.name), slug: slug.name });
33989
+ } else if (entry2.isDirectory() && (entry2.name === "subagents" || entry2.name === "archive")) {
33990
+ const subDir = join59(slugDir, entry2.name);
33942
33991
  let subs;
33943
33992
  try {
33944
33993
  subs = await readdir8(subDir, { withFileTypes: true });
@@ -35326,12 +35375,20 @@ var InboundHub2 = class {
35326
35375
  /** Task-962 — terminal-drop one still-queued in-flight message by id. Returns
35327
35376
  * whether a matching queued entry was found and removed. */
35328
35377
  dropQueued(key2, messageId) {
35378
+ return this.takeQueued(key2, messageId) !== null;
35379
+ }
35380
+ /** Task 2466 — remove one still-queued in-flight message by id and return its
35381
+ * payload, so the caller can re-route it. `dropQueued` discards; this hands
35382
+ * over, and the two share this one removal so a message can never be both
35383
+ * dropped and re-routed. Null when no matching queued entry exists — a
35384
+ * message that has already drained, acked, or been dropped is not takeable. */
35385
+ takeQueued(key2, messageId) {
35329
35386
  const s = this.keys.get(key2);
35330
- if (!s) return false;
35387
+ if (!s) return null;
35331
35388
  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;
35389
+ if (i < 0) return null;
35390
+ const [entry2] = s.inFlight.splice(i, 1);
35391
+ return entry2.payload;
35335
35392
  }
35336
35393
  /** Whether a key currently has a live channel server attached. The gateway
35337
35394
  * uses this to decide whether an inbound needs a cold-start spawn/resume. */
@@ -35573,6 +35630,12 @@ var WebchatGateway = class _WebchatGateway {
35573
35630
  /** Task-962 — `${key}::${messageId}` for messages a recovery call has already
35574
35631
  * been issued for, so recovery is attempted at most once per message. */
35575
35632
  recoveryAttempted = /* @__PURE__ */ new Set();
35633
+ /** Task 2465 — which session each conversation key's child is running. Written
35634
+ * at spawn (the admin id is decided before the manager call, so it is already
35635
+ * known there) and at turn-end (which carries key AND sessionId, so the map
35636
+ * survives a UI restart for any child that has completed a turn). Read only by
35637
+ * auditKeyOrphans. */
35638
+ keySession = /* @__PURE__ */ new Map();
35576
35639
  /** Task-962 — guards against overlapping async recovery passes. */
35577
35640
  recovering = false;
35578
35641
  seq = 0;
@@ -35581,16 +35644,16 @@ var WebchatGateway = class _WebchatGateway {
35581
35644
  * The public /api/chat route awaits this to return the reply on the same SSE
35582
35645
  * response, preserving the pre-756 POST→single-blob browser contract. */
35583
35646
  awaitReply(key2, timeoutMs) {
35584
- return new Promise((resolve51) => {
35647
+ return new Promise((resolve52) => {
35585
35648
  const timer = setTimeout(() => {
35586
35649
  this.replyAwaiters.delete(key2);
35587
- resolve51({ timeout: true });
35650
+ resolve52({ timeout: true });
35588
35651
  }, timeoutMs);
35589
35652
  if (timer.unref) timer.unref();
35590
35653
  this.replyAwaiters.set(key2, (r) => {
35591
35654
  clearTimeout(timer);
35592
35655
  this.replyAwaiters.delete(key2);
35593
- resolve51(r);
35656
+ resolve52(r);
35594
35657
  });
35595
35658
  });
35596
35659
  }
@@ -35601,11 +35664,11 @@ var WebchatGateway = class _WebchatGateway {
35601
35664
  * terminal dialog stays answerable). */
35602
35665
  awaitPermissionVerdict(p, timeoutMs) {
35603
35666
  const k = _WebchatGateway.promptKey(p.key, p.requestId);
35604
- return new Promise((resolve51) => {
35667
+ return new Promise((resolve52) => {
35605
35668
  this.pendingPrompts.get(k)?.resolve({ timeout: true });
35606
35669
  const timer = setTimeout(() => {
35607
35670
  this.pendingPrompts.delete(k);
35608
- resolve51({ timeout: true });
35671
+ resolve52({ timeout: true });
35609
35672
  }, timeoutMs);
35610
35673
  if (timer.unref) timer.unref();
35611
35674
  this.pendingPrompts.set(k, {
@@ -35615,7 +35678,7 @@ var WebchatGateway = class _WebchatGateway {
35615
35678
  clearTimeout(timer);
35616
35679
  this.pendingPrompts.delete(k);
35617
35680
  this.deps.onPointerChange?.(p.key);
35618
- resolve51(r);
35681
+ resolve52(r);
35619
35682
  }
35620
35683
  });
35621
35684
  console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
@@ -35626,9 +35689,9 @@ var WebchatGateway = class _WebchatGateway {
35626
35689
  * false (and is a no-op) when no prompt with this key+id is open — a late or
35627
35690
  * duplicate click. */
35628
35691
  resolvePermissionVerdict(key2, requestId, behavior) {
35629
- const entry = this.pendingPrompts.get(_WebchatGateway.promptKey(key2, requestId));
35630
- if (!entry) return false;
35631
- entry.resolve({ behavior });
35692
+ const entry2 = this.pendingPrompts.get(_WebchatGateway.promptKey(key2, requestId));
35693
+ if (!entry2) return false;
35694
+ entry2.resolve({ behavior });
35632
35695
  console.error(`[webchat:perm] op=verdict key=${keyDisplay(key2)} id=${requestId} behavior=${behavior}`);
35633
35696
  return true;
35634
35697
  }
@@ -35771,6 +35834,7 @@ var WebchatGateway = class _WebchatGateway {
35771
35834
  }
35772
35835
  },
35773
35836
  onTurnEnd: (key2, role, turn) => {
35837
+ this.keySession.set(key2, turn.sessionId);
35774
35838
  if (role === "admin") {
35775
35839
  this.lastDeliveredAt.delete(key2);
35776
35840
  return "none";
@@ -35801,6 +35865,7 @@ var WebchatGateway = class _WebchatGateway {
35801
35865
  });
35802
35866
  for (const e of events) {
35803
35867
  console.error(`[webchat:inbound] op=${e.kind} key=${e.key} messageId=${e.messageId} ageMs=${e.ageMs}`);
35868
+ this.deliveryFailures.set(e.key, { messageId: e.messageId, at: now });
35804
35869
  }
35805
35870
  return events;
35806
35871
  }
@@ -35830,6 +35895,7 @@ var WebchatGateway = class _WebchatGateway {
35830
35895
  run: () => {
35831
35896
  const now = Date.now();
35832
35897
  const events = this.reconcile(now);
35898
+ this.auditKeyOrphans(now);
35833
35899
  void this.recoverStalled(now);
35834
35900
  return events.length > 0 ? `${events.length} terminal events` : void 0;
35835
35901
  }
@@ -35846,6 +35912,60 @@ var WebchatGateway = class _WebchatGateway {
35846
35912
  clearDeliveryFailure(key2) {
35847
35913
  this.deliveryFailures.delete(key2);
35848
35914
  }
35915
+ /** Task 2465 — record which session a conversation key's child is running.
35916
+ * Public so the audit below can be driven directly in tests without standing
35917
+ * up the channel-server HTTP surface; production writes come from runSpawn and
35918
+ * the turn-end handler. */
35919
+ noteKeySession(key2, sessionId) {
35920
+ this.keySession.set(key2, sessionId);
35921
+ }
35922
+ /** Task 2465 — STANDING RECONCILIATION: a queued message on a key no live child
35923
+ * subscribes to, while the session that key names IS live under a different
35924
+ * key. Expected against actual, on the sweep tick.
35925
+ *
35926
+ * There is no action to hang this on and re-running the send reproduces the
35927
+ * same silence, which is why it is an audit rather than another action log.
35928
+ * Before this, the state emitted nothing until the 30s recovery tick, and the
35929
+ * queued key and the live key never appeared on one line anywhere in
35930
+ * server.log — the reason the 2026-08-06 incident read as a wedge throughout.
35931
+ * `liveKey` is the whole diagnosis, and it fires on the FIRST occurrence.
35932
+ *
35933
+ * Covers: a queued key with no subscriber whose named session is held by a
35934
+ * different attached key. Does NOT cover a live child this process neither
35935
+ * spawned nor saw finish a turn — it is absent from keySession, and this is
35936
+ * silent for it. Silent when the keys agree, which is every healthy send.
35937
+ *
35938
+ * Public for the same reason noteKeySession is.
35939
+ *
35940
+ * Task 2466 — the live-key scan moved to sessionSubscriberKey, which the
35941
+ * recovery pass also reads. The audit's own behaviour is unchanged. */
35942
+ auditKeyOrphans(now) {
35943
+ for (const c of this.hub.staleQueued(now, 0)) {
35944
+ const sessionId = c.key.startsWith("session:") ? c.key.slice("session:".length) : this.keySession.get(c.key);
35945
+ if (!sessionId) continue;
35946
+ const liveKey = this.sessionSubscriberKey(sessionId);
35947
+ if (liveKey === null || liveKey === c.key) continue;
35948
+ console.error(
35949
+ `[webchat:inbound] op=key-orphan key=${c.key} messageId=${c.messageId} ageMs=${c.ageMs} liveKey=${liveKey} sessionId=${sessionId.slice(0, 8)}`
35950
+ );
35951
+ }
35952
+ }
35953
+ /** Task 2466 — the conversation key whose channel server is attached right now
35954
+ * and which this gateway records as serving `sessionId`, or null when no such
35955
+ * key exists. The one place "is this session reachable over its channel" is
35956
+ * decided; the orphan audit above and the recovery pass below both read it.
35957
+ *
35958
+ * Both readers are recovery-shaped, so the caller always holds a key with no
35959
+ * subscriber and this can never return that same key. Blind, by construction,
35960
+ * to a live child this process neither spawned nor saw finish a turn: it is
35961
+ * absent from keySession, and the answer is null. */
35962
+ sessionSubscriberKey(sessionId) {
35963
+ for (const [liveKey, liveSession] of this.keySession) {
35964
+ if (liveSession !== sessionId) continue;
35965
+ if (this.hub.hasSubscriber(liveKey)) return liveKey;
35966
+ }
35967
+ return null;
35968
+ }
35849
35969
  /** Task-962 — recovery-aware pass over queued-no-subscriber messages.
35850
35970
  * - age in [RECOVER_TRIGGER_MS, DELIVER_FINAL_MS): ask the manager to recover
35851
35971
  * the live session ONCE. busy → leave queued for the channel server's
@@ -35874,15 +35994,19 @@ var WebchatGateway = class _WebchatGateway {
35874
35994
  const d = this.recoverable.get(c.key);
35875
35995
  if (!d || !this.deps.recoverLiveSession) {
35876
35996
  this.hub.dropQueued(c.key, c.messageId);
35997
+ this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
35877
35998
  console.error(
35878
35999
  `[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=no-descriptor`
35879
36000
  );
35880
36001
  continue;
35881
36002
  }
35882
36003
  this.recoveryAttempted.add(attemptKey);
36004
+ const recoverSessionId = c.key.startsWith("session:") ? c.key.slice("session:".length) : this.keySession.get(c.key) ?? d.sessionId;
36005
+ const liveKey = recoverSessionId === void 0 ? null : this.sessionSubscriberKey(recoverSessionId);
36006
+ const subscriberAttached = liveKey !== null;
35883
36007
  let r;
35884
36008
  try {
35885
- r = await this.deps.recoverLiveSession(d);
36009
+ r = await this.deps.recoverLiveSession({ ...d, subscriberAttached });
35886
36010
  } catch (err) {
35887
36011
  console.error(
35888
36012
  `[webchat:inbound] op=recover-error key=${keyDisplay(c.key)} messageId=${c.messageId} err=${err instanceof Error ? err.message : String(err)}`
@@ -35892,6 +36016,7 @@ var WebchatGateway = class _WebchatGateway {
35892
36016
  if (!r.live) {
35893
36017
  this.hub.dropQueued(c.key, c.messageId);
35894
36018
  this.recoveryAttempted.delete(attemptKey);
36019
+ this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
35895
36020
  console.error(
35896
36021
  `[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=not-live`
35897
36022
  );
@@ -35899,6 +36024,16 @@ var WebchatGateway = class _WebchatGateway {
35899
36024
  console.error(
35900
36025
  `[webchat:inbound] op=recover key=${keyDisplay(c.key)} messageId=${c.messageId} classification=${r.classification ?? "unknown"} respawned=${r.recovered === true}`
35901
36026
  );
36027
+ if (r.classification === "busy" && liveKey !== null && liveKey !== c.key) {
36028
+ const handed = this.hub.takeQueued(c.key, c.messageId);
36029
+ if (handed) {
36030
+ this.recoveryAttempted.delete(attemptKey);
36031
+ this.hub.deliver({ ...handed, key: liveKey }, now);
36032
+ console.error(
36033
+ `[webchat:inbound] op=recover-redeliver key=${c.key} liveKey=${liveKey} messageId=${c.messageId} ageMs=${c.ageMs}`
36034
+ );
36035
+ }
36036
+ }
35902
36037
  }
35903
36038
  }
35904
36039
  for (const key2 of this.recoverable.keys()) {
@@ -35963,6 +36098,10 @@ var WebchatGateway = class _WebchatGateway {
35963
36098
  accountId: input.accountId,
35964
36099
  key: input.key,
35965
36100
  role: input.role,
36101
+ // Task 2466 — a template value. Reachability is a fact about NOW, not
36102
+ // about when the message arrived, so recoverStalled re-stamps it on the
36103
+ // copy it hands to the manager.
36104
+ subscriberAttached: false,
35966
36105
  ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
35967
36106
  ...input.adminUserId !== void 0 ? { adminUserId: input.adminUserId } : {}
35968
36107
  });
@@ -36011,6 +36150,9 @@ var WebchatGateway = class _WebchatGateway {
36011
36150
  lastActivityAt: Date.now()
36012
36151
  });
36013
36152
  }
36153
+ if (input.role === "admin" && input.sessionId !== void 0) {
36154
+ this.keySession.set(input.key, input.sessionId);
36155
+ }
36014
36156
  } catch (err) {
36015
36157
  const reason = (err instanceof Error ? err.message : String(err)).replace(/\s+/g, " ");
36016
36158
  console.error(
@@ -36023,7 +36165,60 @@ var WebchatGateway = class _WebchatGateway {
36023
36165
  }
36024
36166
  };
36025
36167
 
36026
- // app/lib/channel-pty-bridge/follower.ts
36168
+ // app/lib/channel-delivery/answer-accumulator.ts
36169
+ var AnswerAccumulator = class {
36170
+ openId = "";
36171
+ buf = "";
36172
+ open = false;
36173
+ emitted = false;
36174
+ /** Feed one JSONL line. Returns every answer completed by this line, in order:
36175
+ * at most the previous message's, then this one's. */
36176
+ push(line) {
36177
+ if (!line) return [];
36178
+ let row;
36179
+ try {
36180
+ row = JSON.parse(line);
36181
+ } catch {
36182
+ return [];
36183
+ }
36184
+ if (row.type !== "assistant") return this.complete();
36185
+ const msg = row.message;
36186
+ const id = typeof msg?.id === "string" ? msg.id : "";
36187
+ const out = [];
36188
+ if (!this.open || id !== this.openId) {
36189
+ out.push(...this.complete());
36190
+ this.openId = id;
36191
+ this.buf = "";
36192
+ this.open = true;
36193
+ this.emitted = false;
36194
+ }
36195
+ if (Array.isArray(msg?.content)) {
36196
+ for (const block of msg.content) {
36197
+ if (block?.type === "text" && typeof block.text === "string") this.buf += block.text;
36198
+ }
36199
+ }
36200
+ if (!this.emitted && msg?.stop_reason === "end_turn" && this.buf.trim().length > 0) {
36201
+ this.emitted = true;
36202
+ out.push({ messageId: this.openId, text: this.buf });
36203
+ }
36204
+ return out;
36205
+ }
36206
+ /** Complete any open message. Called when the stream ends. */
36207
+ flush() {
36208
+ return this.complete();
36209
+ }
36210
+ complete() {
36211
+ if (!this.open) return [];
36212
+ const answer = !this.emitted && this.buf.trim().length > 0 ? { messageId: this.openId, text: this.buf } : null;
36213
+ this.open = false;
36214
+ this.openId = "";
36215
+ this.buf = "";
36216
+ this.emitted = false;
36217
+ return answer ? [answer] : [];
36218
+ }
36219
+ };
36220
+
36221
+ // app/lib/channel-delivery/emitter.ts
36027
36222
  function followerPendingMaxMs() {
36028
36223
  return Number(process.env.CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS ?? String(3e5));
36029
36224
  }
@@ -36037,19 +36232,31 @@ function toolResultText(content) {
36037
36232
  (b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : ""
36038
36233
  ).join("");
36039
36234
  }
36040
- function startFollower(opts) {
36235
+ function startEmitter(opts) {
36041
36236
  const abort2 = new AbortController();
36042
- const { entry, tag } = opts;
36237
+ const { entry: entry2, tag } = opts;
36238
+ const answers = new AnswerAccumulator();
36239
+ const emitAnswer = async (answer) => {
36240
+ if (answer.text.trim()) await fanOut2(entry2.subscribers, answer.text, opts.onError, tag);
36241
+ if (!opts.onAnswer) return;
36242
+ try {
36243
+ await opts.onAnswer(answer);
36244
+ } catch (err) {
36245
+ console.error(
36246
+ `${tag} answer-error sessionId=${entry2.sessionId.slice(0, 8)} messageId=${answer.messageId} message=${err instanceof Error ? err.message : String(err)}`
36247
+ );
36248
+ }
36249
+ };
36043
36250
  void (async () => {
36044
36251
  try {
36045
- const sid = entry.sessionId.slice(0, 8);
36252
+ const sid = entry2.sessionId.slice(0, 8);
36046
36253
  const deadline = Date.now() + followerPendingMaxMs();
36047
36254
  const retryMs = followerRetryMs();
36048
36255
  let res;
36049
36256
  let attempt = 0;
36050
36257
  for (; ; ) {
36051
36258
  res = await fetch(
36052
- managerLogFollowUrl(entry.sessionId, { boundary: opts.suppressResumeReplay === true }),
36259
+ managerLogFollowUrl(entry2.sessionId, { boundary: opts.suppressResumeReplay === true }),
36053
36260
  { signal: abort2.signal }
36054
36261
  );
36055
36262
  console.error(`${tag} follower-connect sessionId=${sid} status=${res.status}`);
@@ -36083,7 +36290,7 @@ function startFollower(opts) {
36083
36290
  const awaitingVerdict = /* @__PURE__ */ new Set();
36084
36291
  let firedFileTools = [];
36085
36292
  let suppressing = opts.suppressResumeReplay === true;
36086
- let discardedTurns = 0;
36293
+ let discardedAnswers = 0;
36087
36294
  let discardedFileTools = 0;
36088
36295
  while (!abort2.signal.aborted) {
36089
36296
  const { value, done } = await reader.read();
@@ -36100,7 +36307,7 @@ function startFollower(opts) {
36100
36307
  event = JSON.parse(line);
36101
36308
  } catch (err) {
36102
36309
  console.error(
36103
- `${tag} jsonl-parse-skip sessionId=${entry.sessionId.slice(0, 8)} bytes=${line.length} message=${err instanceof Error ? err.message : String(err)}`
36310
+ `${tag} jsonl-parse-skip sessionId=${entry2.sessionId.slice(0, 8)} bytes=${line.length} message=${err instanceof Error ? err.message : String(err)}`
36104
36311
  );
36105
36312
  continue;
36106
36313
  }
@@ -36108,11 +36315,18 @@ function startFollower(opts) {
36108
36315
  if (suppressing) {
36109
36316
  suppressing = false;
36110
36317
  console.error(
36111
- `${tag} follower-resume-boundary sessionId=${sid} discarded-head-turns=${discardedTurns} discarded-head-filetools=${discardedFileTools}`
36318
+ `${tag} emitter-resume-boundary sessionId=${sid} discarded-head-answers=${discardedAnswers} discarded-head-filetools=${discardedFileTools}`
36112
36319
  );
36113
36320
  }
36114
36321
  continue;
36115
36322
  }
36323
+ for (const answer of answers.push(line)) {
36324
+ if (suppressing) {
36325
+ discardedAnswers += 1;
36326
+ continue;
36327
+ }
36328
+ await emitAnswer(answer);
36329
+ }
36116
36330
  if (event.type === "user") {
36117
36331
  if (captureVerdicts && awaitingVerdict.size > 0 && !suppressing) {
36118
36332
  const umsg = event.message;
@@ -36136,7 +36350,6 @@ function startFollower(opts) {
36136
36350
  }
36137
36351
  }
36138
36352
  }
36139
- entry.pendingTurnText = "";
36140
36353
  firedFileTools = [];
36141
36354
  continue;
36142
36355
  }
@@ -36147,7 +36360,6 @@ function startFollower(opts) {
36147
36360
  if (Array.isArray(msg.content)) {
36148
36361
  for (const block of msg.content) {
36149
36362
  if (block?.type === "text" && typeof block.text === "string") {
36150
- entry.pendingTurnText += block.text;
36151
36363
  sawContent = true;
36152
36364
  } else if (block?.type === "tool_use") {
36153
36365
  sawContent = true;
@@ -36172,16 +36384,9 @@ function startFollower(opts) {
36172
36384
  }
36173
36385
  if (msg.stop_reason === "end_turn") {
36174
36386
  if (suppressing) {
36175
- entry.pendingTurnText = "";
36176
36387
  firedFileTools = [];
36177
- discardedTurns += 1;
36178
36388
  continue;
36179
36389
  }
36180
- const flush = entry.pendingTurnText;
36181
- entry.pendingTurnText = "";
36182
- if (flush.trim()) {
36183
- await fanOut2(entry.subscribers, flush, opts.onError, tag);
36184
- }
36185
36390
  if (fileDelivery && (firedFileTools.length > 0 || captureVerdicts)) {
36186
36391
  const fired = firedFileTools;
36187
36392
  firedFileTools = [];
@@ -36200,9 +36405,10 @@ function startFollower(opts) {
36200
36405
  }
36201
36406
  }
36202
36407
  }
36408
+ for (const answer of answers.flush()) await emitAnswer(answer);
36203
36409
  } catch (err) {
36204
36410
  if (!abort2.signal.aborted) {
36205
- opts.onError?.(`follower-error: ${err instanceof Error ? err.message : String(err)}`);
36411
+ opts.onError?.(`emitter-error: ${err instanceof Error ? err.message : String(err)}`);
36206
36412
  }
36207
36413
  } finally {
36208
36414
  opts.onClose();
@@ -36223,14 +36429,42 @@ async function fanOut2(subscribers, text, onError, tag) {
36223
36429
  );
36224
36430
  }
36225
36431
 
36432
+ // app/lib/channel-delivery/parity-census.ts
36433
+ var sessions2 = /* @__PURE__ */ new Map();
36434
+ function entry(sessionId) {
36435
+ let e = sessions2.get(sessionId);
36436
+ if (!e) {
36437
+ e = { rendered: /* @__PURE__ */ new Set(), sent: /* @__PURE__ */ new Set() };
36438
+ sessions2.set(sessionId, e);
36439
+ }
36440
+ return e;
36441
+ }
36442
+ function noteAnswer(sessionId, messageId) {
36443
+ entry(sessionId).rendered.add(messageId);
36444
+ }
36445
+ function noteSent(sessionId, messageId, channels) {
36446
+ if (channels.length === 0) return;
36447
+ entry(sessionId).sent.add(messageId);
36448
+ }
36449
+ function parityCensusLines() {
36450
+ const out = [];
36451
+ for (const [sessionId, e] of sessions2) {
36452
+ const missing = [...e.rendered].filter((id) => !e.sent.has(id));
36453
+ out.push(
36454
+ `[channel-parity] op=parity-census sessionId=${sessionId} rendered=${e.rendered.size} sent=${e.sent.size} missing=${missing.length} missingIds=${missing.join(",") || "none"}`
36455
+ );
36456
+ }
36457
+ return out;
36458
+ }
36459
+
36226
36460
  // app/lib/webchat/file-delivery.ts
36227
36461
  import { realpathSync as realpathSync9 } from "fs";
36228
- import { resolve as resolve46 } from "path";
36462
+ import { resolve as resolve47 } from "path";
36229
36463
 
36230
36464
  // app/lib/channel-pty-bridge/file-delivery.ts
36231
36465
  var SEND_USER_FILE = "SendUserFile";
36232
36466
  function makeFileDelivery(opts) {
36233
- const { entry, tag, channel, sendFile, deferUntilVerdict = false } = opts;
36467
+ const { entry: entry2, tag, channel, sendFile, deferUntilVerdict = false } = opts;
36234
36468
  let failedFiles = [];
36235
36469
  let attempts = 0;
36236
36470
  const pending = /* @__PURE__ */ new Map();
@@ -36257,7 +36491,7 @@ function makeFileDelivery(opts) {
36257
36491
  function unreconciledCall(reason, files, detail) {
36258
36492
  const d = detail ? ` detail="${detail.replace(/\s+/g, " ").slice(0, 120)}"` : "";
36259
36493
  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}`
36494
+ `${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${entry2.sessionId.slice(0, 8)} tool=${SEND_USER_FILE} reason=${reason} files=${files}${d}`
36261
36495
  );
36262
36496
  }
36263
36497
  const handler = {
@@ -36278,10 +36512,10 @@ function makeFileDelivery(opts) {
36278
36512
  const tried = attempts;
36279
36513
  failedFiles = [];
36280
36514
  attempts = 0;
36281
- const sid = entry.sessionId.slice(0, 8);
36515
+ const sid = entry2.sessionId.slice(0, 8);
36282
36516
  for (const file of failed2) {
36283
36517
  console.error(
36284
- `${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
36518
+ `${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
36285
36519
  );
36286
36520
  }
36287
36521
  if (deferUntilVerdict) {
@@ -36291,7 +36525,7 @@ function makeFileDelivery(opts) {
36291
36525
  }
36292
36526
  if (firedTools.includes(SEND_USER_FILE) && tried === 0) {
36293
36527
  console.error(
36294
- `${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
36528
+ `${tag} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
36295
36529
  );
36296
36530
  }
36297
36531
  }
@@ -36320,43 +36554,43 @@ var TAG58 = "[webchat-adaptor]";
36320
36554
  function platformRoot2() {
36321
36555
  return process.env.MAXY_PLATFORM_ROOT || "";
36322
36556
  }
36323
- function makeWebchatSendFile(entry) {
36557
+ function makeWebchatSendFile(entry2) {
36324
36558
  return async (filePath) => {
36325
- if (!entry.accountId) {
36326
- console.error(`${TAG58} file-delivery reject reason=no-account sender=${entry.senderId}`);
36559
+ if (!entry2.accountId) {
36560
+ console.error(`${TAG58} file-delivery reject reason=no-account sender=${entry2.senderId}`);
36327
36561
  return { ok: false, error: "no-account" };
36328
36562
  }
36329
- const accountDir = resolve46(platformRoot2(), "..", "data/accounts", entry.accountId);
36563
+ const accountDir = resolve47(platformRoot2(), "..", "data/accounts", entry2.accountId);
36330
36564
  try {
36331
36565
  const resolved = realpathSync9(filePath);
36332
36566
  const accountResolved = realpathSync9(accountDir);
36333
36567
  if (!resolved.startsWith(accountResolved + "/")) {
36334
- console.error(`${TAG58} file-delivery reject reason=outside_account_directory sender=${entry.senderId}`);
36568
+ console.error(`${TAG58} file-delivery reject reason=outside_account_directory sender=${entry2.senderId}`);
36335
36569
  return { ok: false, error: "outside-account" };
36336
36570
  }
36337
36571
  return { ok: true };
36338
36572
  } catch (err) {
36339
36573
  const code = err.code;
36340
36574
  console.error(
36341
- `${TAG58} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry.senderId}`
36575
+ `${TAG58} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry2.senderId}`
36342
36576
  );
36343
36577
  return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
36344
36578
  }
36345
36579
  };
36346
36580
  }
36347
- function makeWebchatFileDelivery(entry) {
36581
+ function makeWebchatFileDelivery(entry2) {
36348
36582
  return makeFileDelivery({
36349
- entry,
36583
+ entry: entry2,
36350
36584
  tag: TAG58,
36351
36585
  channel: "webchat",
36352
- sendFile: makeWebchatSendFile(entry),
36586
+ sendFile: makeWebchatSendFile(entry2),
36353
36587
  deferUntilVerdict: true
36354
36588
  });
36355
36589
  }
36356
36590
 
36357
36591
  // app/lib/webchat/gateway/native-file-follower.ts
36358
36592
  function startWebchatNativeFileFollower(input) {
36359
- const entry = {
36593
+ const entry2 = {
36360
36594
  sessionId: input.sessionId,
36361
36595
  role: "admin",
36362
36596
  channel: "webchat",
@@ -36366,7 +36600,6 @@ function startWebchatNativeFileFollower(input) {
36366
36600
  lastInboundAt: Date.now(),
36367
36601
  writeChain: Promise.resolve(),
36368
36602
  subscribers: /* @__PURE__ */ new Set(),
36369
- pendingTurnText: "",
36370
36603
  followerAbort: null,
36371
36604
  followerRunning: false,
36372
36605
  sliceToken: "",
@@ -36375,13 +36608,24 @@ function startWebchatNativeFileFollower(input) {
36375
36608
  // Webchat renders from the transcript; there is no separate reply target.
36376
36609
  replyTarget: null
36377
36610
  };
36378
- return startFollower({
36379
- entry,
36611
+ return startEmitter({
36612
+ entry: entry2,
36380
36613
  tag: "[webchat-adaptor]",
36381
- fileDelivery: makeWebchatFileDelivery(entry),
36614
+ fileDelivery: makeWebchatFileDelivery(entry2),
36382
36615
  // A resumed session's JSONL already holds prior SendUserFile tool_uses;
36383
36616
  // suppress replay so historical calls are not re-reconciled on attach.
36384
36617
  suppressResumeReplay: true,
36618
+ // Task 2521 — the answer reaches every bound door through the one fan-out.
36619
+ // Ungated by design: whatever woke the turn, the reader and the chats show
36620
+ // the same thing.
36621
+ onAnswer: async (answer) => {
36622
+ const targets = input.fanTargets?.() ?? [];
36623
+ if (targets.length === 0) return;
36624
+ noteAnswer(input.sessionId, answer.messageId);
36625
+ const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
36626
+ noteSent(input.sessionId, answer.messageId, reached);
36627
+ console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
36628
+ },
36385
36629
  onClose: input.onClose ?? (() => {
36386
36630
  })
36387
36631
  });
@@ -36417,29 +36661,29 @@ var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
36417
36661
  function platformRoot3() {
36418
36662
  return process.env.MAXY_PLATFORM_ROOT || "";
36419
36663
  }
36420
- function makeWhatsAppSendFile(entry, maxyAccountId) {
36664
+ function makeWhatsAppSendFile(entry2, maxyAccountId) {
36421
36665
  return async (filePath, caption) => {
36422
36666
  const result = await sendWhatsAppDocument({
36423
- to: entry.senderId,
36667
+ to: entry2.senderId,
36424
36668
  filePath,
36425
36669
  caption,
36426
- accountId: entry.accountId,
36670
+ accountId: entry2.accountId,
36427
36671
  maxyAccountId,
36428
36672
  platformRoot: platformRoot3()
36429
36673
  });
36430
36674
  if (result.ok) return { ok: true };
36431
36675
  console.error(
36432
- `${TAG59} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
36676
+ `${TAG59} file-delivery reject reason=send-failed sender=${entry2.senderId} status=${result.status} message=${result.error}`
36433
36677
  );
36434
36678
  return { ok: false, error: result.error };
36435
36679
  };
36436
36680
  }
36437
- function makeWhatsAppFileDelivery(entry, maxyAccountId) {
36681
+ function makeWhatsAppFileDelivery(entry2, maxyAccountId) {
36438
36682
  const shared = makeFileDelivery({
36439
- entry,
36683
+ entry: entry2,
36440
36684
  tag: TAG59,
36441
36685
  channel: "whatsapp",
36442
- sendFile: makeWhatsAppSendFile(entry, maxyAccountId)
36686
+ sendFile: makeWhatsAppSendFile(entry2, maxyAccountId)
36443
36687
  });
36444
36688
  let turnStartedAt = null;
36445
36689
  let routeCalls = [];
@@ -36464,7 +36708,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
36464
36708
  const routes = routeCalls;
36465
36709
  turnStartedAt = null;
36466
36710
  routeCalls = [];
36467
- const sid = entry.sessionId.slice(0, 8);
36711
+ const sid = entry2.sessionId.slice(0, 8);
36468
36712
  shared.onTurnEnd(firedTools);
36469
36713
  for (const call of routes) {
36470
36714
  const routeAt = call.to !== void 0 && call.filePath !== void 0 ? routeDocumentOutboundAt(call.to, call.filePath) : void 0;
@@ -36472,7 +36716,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
36472
36716
  if (!delivered) {
36473
36717
  const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
36474
36718
  console.error(
36475
- `${TAG59} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
36719
+ `${TAG59} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
36476
36720
  );
36477
36721
  }
36478
36722
  }
@@ -36483,17 +36727,16 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
36483
36727
  // app/lib/whatsapp/gateway/native-file-follower.ts
36484
36728
  var COMPOSING_REFRESH_MS = 1e4;
36485
36729
  function startNativeFileFollower(input) {
36486
- const entry = {
36730
+ const entry2 = {
36487
36731
  sessionId: input.sessionId,
36488
- role: "admin",
36732
+ role: input.role,
36489
36733
  channel: "whatsapp",
36490
36734
  accountId: input.accountId,
36491
36735
  senderId: input.senderId,
36492
- agentSlug: "admin",
36736
+ agentSlug: input.role === "admin" ? "admin" : "default",
36493
36737
  lastInboundAt: Date.now(),
36494
36738
  writeChain: Promise.resolve(),
36495
36739
  subscribers: /* @__PURE__ */ new Set(),
36496
- pendingTurnText: "",
36497
36740
  followerAbort: null,
36498
36741
  followerRunning: false,
36499
36742
  sliceToken: "",
@@ -36529,15 +36772,28 @@ function startNativeFileFollower(input) {
36529
36772
  if (!sock) return;
36530
36773
  void sendPaused(sock, input.senderId);
36531
36774
  };
36532
- return startFollower({
36533
- entry,
36775
+ return startEmitter({
36776
+ entry: entry2,
36534
36777
  tag: "[whatsapp-adaptor]",
36535
- fileDelivery: makeWhatsAppFileDelivery(entry, input.maxyAccountId),
36778
+ // Task 2521 — admin-only. A public spawn has no tools (Task 2078), so a
36779
+ // handler here would reconcile a call that can never fire.
36780
+ fileDelivery: input.role === "admin" ? makeWhatsAppFileDelivery(entry2, input.maxyAccountId) : null,
36536
36781
  // A resumed session's JSONL already holds prior SendUserFile tool_uses;
36537
36782
  // suppress replay so historical files are not re-sent on attach.
36538
36783
  suppressResumeReplay: true,
36539
36784
  onActivity,
36540
36785
  onTurnComplete,
36786
+ // Task 2521 — the answer reaches every bound door through the one fan-out.
36787
+ // Ungated by design: whatever woke the turn, the reader and the chats show
36788
+ // the same thing.
36789
+ onAnswer: async (answer) => {
36790
+ const targets = input.fanTargets?.() ?? [];
36791
+ if (targets.length === 0) return;
36792
+ noteAnswer(input.sessionId, answer.messageId);
36793
+ const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
36794
+ noteSent(input.sessionId, answer.messageId, reached);
36795
+ console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
36796
+ },
36541
36797
  onClose: input.onClose ?? (() => {
36542
36798
  })
36543
36799
  });
@@ -37090,6 +37346,9 @@ function buildTelegramEnsureSession(deps) {
37090
37346
  accountId: input.accountId,
37091
37347
  chatId: input.chatId,
37092
37348
  botToken: input.botToken,
37349
+ // Task 2521 — resolved per answer, not captured once, so a door that goes
37350
+ // live after this session started still receives.
37351
+ fanTargets: () => deps.fanTargets(req.channelKey),
37093
37352
  // Identity-guarded eviction: only delete if this controller is still the
37094
37353
  // current one, so a stale self-close never removes a live follower.
37095
37354
  onClose: () => {
@@ -37104,7 +37363,7 @@ function buildTelegramEnsureSession(deps) {
37104
37363
  init_attachments();
37105
37364
  import { realpathSync as realpathSync10 } from "fs";
37106
37365
  import { readFile as readFile14, stat as stat10 } from "fs/promises";
37107
- import { resolve as resolve47, basename as basename16 } from "path";
37366
+ import { resolve as resolve48, basename as basename16 } from "path";
37108
37367
  var TAG61 = "[telegram:outbound]";
37109
37368
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
37110
37369
  async function sendTelegramDocument(input) {
@@ -37115,7 +37374,7 @@ async function sendTelegramDocument(input) {
37115
37374
  if (!maxyAccountId || !platformRoot5) {
37116
37375
  return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
37117
37376
  }
37118
- const accountDir = resolve47(platformRoot5, "..", "data/accounts", maxyAccountId);
37377
+ const accountDir = resolve48(platformRoot5, "..", "data/accounts", maxyAccountId);
37119
37378
  let accountResolved;
37120
37379
  try {
37121
37380
  accountResolved = realpathSync10(accountDir);
@@ -37184,39 +37443,39 @@ var TAG62 = "[telegram:outbound]";
37184
37443
  function platformRoot4() {
37185
37444
  return process.env.MAXY_PLATFORM_ROOT || "";
37186
37445
  }
37187
- function makeTelegramSendFile(entry, botToken) {
37446
+ function makeTelegramSendFile(entry2, botToken) {
37188
37447
  return async (filePath, caption) => {
37189
37448
  if (!botToken) {
37190
- console.error(`${TAG62} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
37449
+ console.error(`${TAG62} file-delivery reject reason=no-bot-token sender=${entry2.senderId} role=${entry2.role}`);
37191
37450
  return { ok: false, error: "no-bot-token" };
37192
37451
  }
37193
- if (entry.replyTarget == null) {
37194
- console.error(`${TAG62} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
37452
+ if (entry2.replyTarget == null) {
37453
+ console.error(`${TAG62} file-delivery reject reason=no-reply-target sender=${entry2.senderId} role=${entry2.role}`);
37195
37454
  return { ok: false, error: "no-reply-target" };
37196
37455
  }
37197
37456
  const result = await sendTelegramDocument({
37198
37457
  botToken,
37199
- chatId: Number(entry.replyTarget),
37458
+ chatId: Number(entry2.replyTarget),
37200
37459
  filePath,
37201
37460
  caption,
37202
- maxyAccountId: entry.accountId,
37461
+ maxyAccountId: entry2.accountId,
37203
37462
  platformRoot: platformRoot4()
37204
37463
  });
37205
37464
  return result.ok ? { ok: true } : { ok: false, error: result.error };
37206
37465
  };
37207
37466
  }
37208
- function makeTelegramFileDelivery(entry, botToken) {
37467
+ function makeTelegramFileDelivery(entry2, botToken) {
37209
37468
  return makeFileDelivery({
37210
- entry,
37469
+ entry: entry2,
37211
37470
  tag: TAG62,
37212
37471
  channel: "telegram",
37213
- sendFile: makeTelegramSendFile(entry, botToken)
37472
+ sendFile: makeTelegramSendFile(entry2, botToken)
37214
37473
  });
37215
37474
  }
37216
37475
 
37217
37476
  // app/lib/telegram/gateway/native-file-follower.ts
37218
37477
  function startTelegramNativeFileFollower(input) {
37219
- const entry = {
37478
+ const entry2 = {
37220
37479
  sessionId: input.sessionId,
37221
37480
  role: "admin",
37222
37481
  channel: "telegram",
@@ -37226,7 +37485,6 @@ function startTelegramNativeFileFollower(input) {
37226
37485
  lastInboundAt: Date.now(),
37227
37486
  writeChain: Promise.resolve(),
37228
37487
  subscribers: /* @__PURE__ */ new Set(),
37229
- pendingTurnText: "",
37230
37488
  followerAbort: null,
37231
37489
  followerRunning: false,
37232
37490
  sliceToken: "",
@@ -37235,13 +37493,24 @@ function startTelegramNativeFileFollower(input) {
37235
37493
  // The chat id the document is addressed to (set per spawn from the inbound).
37236
37494
  replyTarget: input.chatId
37237
37495
  };
37238
- return startFollower({
37239
- entry,
37496
+ return startEmitter({
37497
+ entry: entry2,
37240
37498
  tag: "[telegram-adaptor]",
37241
- fileDelivery: makeTelegramFileDelivery(entry, input.botToken),
37499
+ fileDelivery: makeTelegramFileDelivery(entry2, input.botToken),
37242
37500
  // A resumed session's JSONL already holds prior SendUserFile tool_uses;
37243
37501
  // suppress replay so historical files are not re-sent on attach.
37244
37502
  suppressResumeReplay: true,
37503
+ // Task 2521 — the answer reaches every bound door through the one fan-out.
37504
+ // Ungated by design: whatever woke the turn, the reader and the chats show
37505
+ // the same thing.
37506
+ onAnswer: async (answer) => {
37507
+ const targets = input.fanTargets?.() ?? [];
37508
+ if (targets.length === 0) return;
37509
+ noteAnswer(input.sessionId, answer.messageId);
37510
+ const { line, reached } = await fanOut({ targets, segments: segmentText(answer.text) });
37511
+ noteSent(input.sessionId, answer.messageId, reached);
37512
+ console.error(`${line} messageId=${answer.messageId} sessionId=${input.sessionId.slice(0, 8)}`);
37513
+ },
37245
37514
  onClose: input.onClose ?? (() => {
37246
37515
  })
37247
37516
  });
@@ -37269,22 +37538,22 @@ function adminFanTargets(p) {
37269
37538
  noTarget(p.origin, p.accountId, "users-read-failed");
37270
37539
  return [origin];
37271
37540
  }
37272
- const entry = (users ?? []).find((u) => u.userId === p.adminUserId);
37273
- if (!entry) {
37541
+ const entry2 = (users ?? []).find((u) => u.userId === p.adminUserId);
37542
+ if (!entry2) {
37274
37543
  noTarget(p.origin, p.accountId, "no-entry");
37275
37544
  return [origin];
37276
37545
  }
37277
- if (!entry.phone && !entry.telegram) {
37546
+ if (!entry2.phone && !entry2.telegram) {
37278
37547
  noTarget(p.origin, p.accountId, "no-bindings");
37279
37548
  return [origin];
37280
37549
  }
37281
37550
  const bound = [];
37282
- if (entry.phone) {
37283
- const wa = p.deps.whatsappLiveTarget(entry.phone);
37551
+ if (entry2.phone) {
37552
+ const wa = p.deps.whatsappLiveTarget(entry2.phone);
37284
37553
  bound.push(wa ? { channel: "whatsapp", live: true, send: wa } : { channel: "whatsapp", live: false });
37285
37554
  }
37286
- if (entry.telegram) {
37287
- const tg = p.deps.telegramLiveTarget(entry.telegram);
37555
+ if (entry2.telegram) {
37556
+ const tg = p.deps.telegramLiveTarget(entry2.telegram);
37288
37557
  bound.push(tg ? { channel: "telegram", live: true, send: tg } : { channel: "telegram", live: false });
37289
37558
  }
37290
37559
  return [origin, ...bound.filter((t) => t.channel !== p.origin)];
@@ -37491,8 +37760,8 @@ init_resolve_admin_userid();
37491
37760
  init_src2();
37492
37761
  function findAccountManagerDrift(accountManagers, validAccountIds) {
37493
37762
  const drift = [];
37494
- for (const [phone, entry] of Object.entries(accountManagers)) {
37495
- const managesAccount = typeof entry === "string" ? entry : entry.managesAccount;
37763
+ for (const [phone, entry2] of Object.entries(accountManagers)) {
37764
+ const managesAccount = typeof entry2 === "string" ? entry2 : entry2.managesAccount;
37496
37765
  if (!validAccountIds.includes(managesAccount)) {
37497
37766
  drift.push({ phone, managesAccount, reason: "not-in-registry" });
37498
37767
  }
@@ -37607,36 +37876,36 @@ init_sidebar_sessions();
37607
37876
  function buildTelegramAuditLines(input) {
37608
37877
  const lines = [];
37609
37878
  const entries2 = input.accounts.flatMap(
37610
- (a) => listBotEntries(a.telegram).map((entry) => ({ accountId: a.accountId, accountDir: a.accountDir, entry }))
37879
+ (a) => listBotEntries(a.telegram).map((entry2) => ({ accountId: a.accountId, accountDir: a.accountDir, entry: entry2 }))
37611
37880
  );
37612
37881
  if (entries2.length === 0) return lines;
37613
37882
  const byId = /* @__PURE__ */ new Map();
37614
- for (const { entry } of entries2) byId.set(entry.id, (byId.get(entry.id) ?? 0) + 1);
37883
+ for (const { entry: entry2 } of entries2) byId.set(entry2.id, (byId.get(entry2.id) ?? 0) + 1);
37615
37884
  for (const [botId, count] of byId) {
37616
37885
  if (count > 1) lines.push(`[telegram-audit] op=duplicate-bot-id botId=${botId} accounts=${count}`);
37617
37886
  }
37618
- for (const { accountId, accountDir, entry } of entries2) {
37619
- if (entry.role === "public" && !input.agentActive(accountDir, entry.agent)) {
37887
+ for (const { accountId, accountDir, entry: entry2 } of entries2) {
37888
+ if (entry2.role === "public" && !input.agentActive(accountDir, entry2.agent)) {
37620
37889
  lines.push(
37621
- `[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${entry.id} agent=${entry.agent}`
37890
+ `[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${entry2.id} agent=${entry2.agent}`
37622
37891
  );
37623
37892
  }
37624
- if (!input.secretFileExists(entry.id)) {
37625
- lines.push(`[telegram-audit] op=secret-orphan botId=${entry.id} side=entry`);
37893
+ if (!input.secretFileExists(entry2.id)) {
37894
+ lines.push(`[telegram-audit] op=secret-orphan botId=${entry2.id} side=entry`);
37626
37895
  }
37627
37896
  }
37628
37897
  const known = new Set(entries2.map((e) => e.entry.id));
37629
37898
  for (const botId of input.secretFilesOnDisk) {
37630
37899
  if (!known.has(botId)) lines.push(`[telegram-audit] op=secret-orphan botId=${botId} side=file`);
37631
37900
  }
37632
- for (const { accountId, entry } of entries2) {
37633
- const last = input.lastActivityByBot(entry.id);
37901
+ for (const { accountId, entry: entry2 } of entries2) {
37902
+ const last = input.lastActivityByBot(entry2.id);
37634
37903
  const spawnAge = last === null ? "never" : String(input.now - last.spawnMs);
37635
37904
  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";
37905
+ const agent = entry2.role === "admin" ? "admin" : entry2.agent;
37906
+ const secretFile = input.secretFileExists(entry2.id) ? "present" : "absent";
37638
37907
  lines.push(
37639
- `[telegram-audit] op=configured botId=${entry.id} accountId=${accountId} agent=${agent} secretFile=${secretFile} lastSpawnAgeMs=${spawnAge} lastTurnAgeMs=${turnAge}`
37908
+ `[telegram-audit] op=configured botId=${entry2.id} accountId=${accountId} agent=${agent} secretFile=${secretFile} lastSpawnAgeMs=${spawnAge} lastTurnAgeMs=${turnAge}`
37640
37909
  );
37641
37910
  }
37642
37911
  return lines;
@@ -37693,7 +37962,7 @@ function* walkTelegramSidecars() {
37693
37962
  }
37694
37963
 
37695
37964
  // server/telegram-migrate.ts
37696
- import { readFileSync as readFileSync51, writeFileSync as writeFileSync19, existsSync as existsSync53, renameSync as renameSync11, mkdirSync as mkdirSync11 } from "fs";
37965
+ import { readFileSync as readFileSync52, writeFileSync as writeFileSync19, existsSync as existsSync53, renameSync as renameSync11, mkdirSync as mkdirSync11 } from "fs";
37697
37966
  import { dirname as dirname18, join as join62 } from "path";
37698
37967
  function migrateTelegramTwoSlot(input) {
37699
37968
  const out = [];
@@ -37701,7 +37970,7 @@ function migrateTelegramTwoSlot(input) {
37701
37970
  const configPath3 = join62(account.accountDir, "account.json");
37702
37971
  let config;
37703
37972
  try {
37704
- config = JSON.parse(readFileSync51(configPath3, "utf-8"));
37973
+ config = JSON.parse(readFileSync52(configPath3, "utf-8"));
37705
37974
  } catch {
37706
37975
  console.error(`[telegram-audit] op=migrate-unreadable accountId=${account.accountId}`);
37707
37976
  continue;
@@ -37803,14 +38072,14 @@ function backfillMisfiledRecords(params) {
37803
38072
  init_message_store();
37804
38073
 
37805
38074
  // server/lib/booking-site-accounts.ts
37806
- import { existsSync as existsSync54, readFileSync as readFileSync52 } from "fs";
37807
- import { resolve as resolve48 } from "path";
38075
+ import { existsSync as existsSync54, readFileSync as readFileSync53 } from "fs";
38076
+ import { resolve as resolve49 } from "path";
37808
38077
  function listBookingSiteAccounts(accounts) {
37809
38078
  return accounts.filter((a) => {
37810
- const availPath = resolve48(a.accountDir, "calendar-availability.json");
38079
+ const availPath = resolve49(a.accountDir, "calendar-availability.json");
37811
38080
  if (!existsSync54(availPath)) return false;
37812
38081
  try {
37813
- const cfg = JSON.parse(readFileSync52(availPath, "utf-8"));
38082
+ const cfg = JSON.parse(readFileSync53(availPath, "utf-8"));
37814
38083
  return typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
37815
38084
  } catch {
37816
38085
  return false;
@@ -37832,17 +38101,17 @@ function broadcastAdminShutdown(reason) {
37832
38101
  const done = encoder.encode(`data: [DONE]
37833
38102
 
37834
38103
  `);
37835
- for (const entry of activeAdminSSEControllers) {
38104
+ for (const entry2 of activeAdminSSEControllers) {
37836
38105
  try {
37837
- entry.controller.enqueue(frame);
38106
+ entry2.controller.enqueue(frame);
37838
38107
  } catch {
37839
38108
  }
37840
38109
  try {
37841
- entry.controller.enqueue(done);
38110
+ entry2.controller.enqueue(done);
37842
38111
  } catch {
37843
38112
  }
37844
38113
  try {
37845
- entry.controller.close();
38114
+ entry2.controller.close();
37846
38115
  } catch {
37847
38116
  }
37848
38117
  }
@@ -37855,8 +38124,8 @@ init_paths();
37855
38124
 
37856
38125
  // ../lib/entitlement/src/index.ts
37857
38126
  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";
38127
+ import { existsSync as existsSync55, readFileSync as readFileSync54, statSync as statSync29 } from "fs";
38128
+ import { resolve as resolve50 } from "path";
37860
38129
 
37861
38130
  // ../lib/entitlement/src/canonicalize.ts
37862
38131
  function canonicalize(value) {
@@ -37891,7 +38160,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
37891
38160
  var GRACE_DAYS = 7;
37892
38161
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
37893
38162
  function pubkeyPath(brand) {
37894
- return resolve49(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
38163
+ return resolve50(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
37895
38164
  }
37896
38165
  var memo = null;
37897
38166
  function memoKey(mtimeMs, account) {
@@ -37903,7 +38172,7 @@ function resolveEntitlement(brand, account) {
37903
38172
  if (brand.commercialMode !== true) {
37904
38173
  return logResolved(implicitTrust(account), null);
37905
38174
  }
37906
- const entitlementPath = resolve49(brand.configDir, "entitlement.json");
38175
+ const entitlementPath = resolve50(brand.configDir, "entitlement.json");
37907
38176
  if (!existsSync55(entitlementPath)) {
37908
38177
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
37909
38178
  }
@@ -37919,7 +38188,7 @@ function resolveEntitlement(brand, account) {
37919
38188
  function verifyAndResolve(brand, entitlementPath, account) {
37920
38189
  let pubkeyPem;
37921
38190
  try {
37922
- pubkeyPem = readFileSync53(pubkeyPath(brand), "utf-8");
38191
+ pubkeyPem = readFileSync54(pubkeyPath(brand), "utf-8");
37923
38192
  } catch (err) {
37924
38193
  return logResolved(anonymousFallback("pubkey-missing"), {
37925
38194
  reason: "pubkey-missing"
@@ -37933,7 +38202,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
37933
38202
  }
37934
38203
  let envelope;
37935
38204
  try {
37936
- envelope = JSON.parse(readFileSync53(entitlementPath, "utf-8"));
38205
+ envelope = JSON.parse(readFileSync54(entitlementPath, "utf-8"));
37937
38206
  } catch {
37938
38207
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
37939
38208
  }
@@ -38102,7 +38371,7 @@ if (BRAND_JSON_PATH && !existsSync56(BRAND_JSON_PATH)) {
38102
38371
  }
38103
38372
  if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
38104
38373
  try {
38105
- const parsed = JSON.parse(readFileSync54(BRAND_JSON_PATH, "utf-8"));
38374
+ const parsed = JSON.parse(readFileSync55(BRAND_JSON_PATH, "utf-8"));
38106
38375
  BRAND = { ...BRAND, ...parsed };
38107
38376
  } catch (err) {
38108
38377
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -38138,7 +38407,7 @@ var ALIAS_DOMAINS_PATH = join63(homedir5(), BRAND.configDir, "alias-domains.json
38138
38407
  function loadAliasDomains() {
38139
38408
  try {
38140
38409
  if (!existsSync56(ALIAS_DOMAINS_PATH)) return null;
38141
- const parsed = JSON.parse(readFileSync54(ALIAS_DOMAINS_PATH, "utf-8"));
38410
+ const parsed = JSON.parse(readFileSync55(ALIAS_DOMAINS_PATH, "utf-8"));
38142
38411
  if (!Array.isArray(parsed)) {
38143
38412
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
38144
38413
  return null;
@@ -38209,6 +38478,23 @@ async function recordPassiveDispatch(input) {
38209
38478
  await session.close();
38210
38479
  }
38211
38480
  }
38481
+ registerLoop({
38482
+ name: "channel-parity",
38483
+ intervalMs: 6e4,
38484
+ run: () => {
38485
+ const lines = parityCensusLines();
38486
+ for (const line of lines) console.error(line);
38487
+ const missing = lines.filter((l) => !l.includes("missing=0")).length;
38488
+ return `${lines.length} session(s), ${missing} with a missing answer`;
38489
+ }
38490
+ });
38491
+ var whatsappFanTargets = (accountId, senderId) => adminFanTargets({
38492
+ origin: "whatsapp",
38493
+ originKey: senderId,
38494
+ accountId,
38495
+ adminUserId: resolveAdminUserId({ accountId, senderPhone: senderId }),
38496
+ deps: adminFanDeps("whatsapp", accountId)
38497
+ });
38212
38498
  var waGateway = new WaGateway({
38213
38499
  fetchStandingRules: fetchAccountStandingRules,
38214
38500
  // Task 2321 — the account's own `## Instruction` body, read from that
@@ -38231,15 +38517,11 @@ var waGateway = new WaGateway({
38231
38517
  // Task 2078 — one answer reaches every live door for this admin person.
38232
38518
  // Task 2459 — the origin door is resolved from `senderId`, so it is present
38233
38519
  // 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
- }),
38520
+ // Task 2521 — hoisted, because the emitter's per-answer fan-out needs the same
38521
+ // resolver and `fanTargetsFor` is a constructor dep, not a callable method.
38522
+ fanTargetsFor: whatsappFanTargets,
38241
38523
  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"),
38524
+ serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve51(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
38243
38525
  // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
38244
38526
  // path-validation scope) is the sender's effective SESSION account, resolved
38245
38527
  // once at the inbound gate and threaded here via the gateway's per-sender doc
@@ -38254,7 +38536,7 @@ var waGateway = new WaGateway({
38254
38536
  caption,
38255
38537
  accountId,
38256
38538
  maxyAccountId,
38257
- platformRoot: resolve50(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."))
38539
+ platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, ".."))
38258
38540
  });
38259
38541
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
38260
38542
  },
@@ -38389,7 +38671,6 @@ var waGateway = new WaGateway({
38389
38671
  console.error(`[whatsapp-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
38390
38672
  return;
38391
38673
  }
38392
- if (role !== "admin") return;
38393
38674
  const prior = nativeFileFollowers.get(senderId);
38394
38675
  if (prior) prior.abort();
38395
38676
  let ac;
@@ -38401,6 +38682,11 @@ var waGateway = new WaGateway({
38401
38682
  // session account (the sub-account for an account-manager), not the house
38402
38683
  // socket account. accountId stays the getSocket key.
38403
38684
  maxyAccountId: effectiveAccountId,
38685
+ // Task 2521 — file delivery is admin-only; the text emitter is not.
38686
+ role,
38687
+ // Task 2521 — resolved per answer, not captured once, so a door that goes
38688
+ // live after this session started still receives.
38689
+ fanTargets: () => whatsappFanTargets(accountId, senderId),
38404
38690
  onClose: () => {
38405
38691
  if (nativeFileFollowers.get(senderId) === ac) nativeFileFollowers.delete(senderId);
38406
38692
  }
@@ -38547,12 +38833,15 @@ var webchatGateway = new WebchatGateway({
38547
38833
  // live admin session (rc-spawn recover:true). Resolve the session id the same
38548
38834
  // way ensureChannelSession does. Public sessions recover via the idle reaper,
38549
38835
  // so a non-admin descriptor is reported not-live (no respawn).
38550
- recoverLiveSession: async ({ accountId, key: key2, role, sessionId, adminUserId }) => {
38836
+ recoverLiveSession: async ({ accountId, key: key2, role, sessionId, adminUserId, subscriberAttached }) => {
38551
38837
  if (role !== "admin") return { live: false };
38552
38838
  const id = sessionId ?? adminSessionIdFor(accountId, key2, adminUserId);
38553
38839
  const r = await managerRcRecover({
38554
38840
  sessionId: id,
38555
38841
  webchatChannel: { key: key2, gatewayUrl: webchatGatewayUrl(), serverPath: webchatServerPath() },
38842
+ // Task 2466 — the gateway resolved this against the live subscriber table
38843
+ // just before the call; the manager classifies from it.
38844
+ subscriberAttached,
38556
38845
  ...adminUserId !== void 0 ? { adminUserId } : {},
38557
38846
  logContext: `channel=webchat key=${key2} recover`
38558
38847
  });
@@ -38573,6 +38862,24 @@ app80.route("/", webchatGateway.routes());
38573
38862
  webchatGateway.startSweeper();
38574
38863
  webchatGateway.startPublicReaper();
38575
38864
  var telegramFileFollowers = /* @__PURE__ */ new Map();
38865
+ var telegramFanTargets = (key2) => {
38866
+ const chatId = key2.slice(key2.indexOf(":") + 1);
38867
+ const account = listValidAccounts().find(
38868
+ (a) => listBotEntries(a.config.telegram).some((e) => e.id === key2.slice(0, key2.indexOf(":")))
38869
+ );
38870
+ if (!account) {
38871
+ console.error(`[channel-fanout] op=no-target origin=telegram key=${key2} accountId=none reason=no-account`);
38872
+ const send = telegramOriginTarget(key2);
38873
+ return [send ? { channel: "telegram", live: true, send } : { channel: "telegram", live: false }];
38874
+ }
38875
+ return adminFanTargets({
38876
+ origin: "telegram",
38877
+ originKey: key2,
38878
+ accountId: account.accountId,
38879
+ adminUserId: resolveTelegramAdminUserId({ accountId: account.accountId, chatId }),
38880
+ deps: adminFanDeps("telegram", account.accountId)
38881
+ });
38882
+ };
38576
38883
  var telegramGateway = new TelegramGateway({
38577
38884
  fetchStandingRules: fetchAccountStandingRules,
38578
38885
  // Task 2353 — no turn-instruction resolver on this door. See the dep comment in
@@ -38582,26 +38889,8 @@ var telegramGateway = new TelegramGateway({
38582
38889
  // is what the classification sidecar records (the manager reads
38583
38890
  // telegramChannel.senderId) and therefore what session-reseat.ts writes.
38584
38891
  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
- },
38892
+ // Task 2521hoisted above; see `telegramFanTargets`.
38893
+ fanTargetsFor: telegramFanTargets,
38605
38894
  gatewayUrl: telegramGatewayUrl(),
38606
38895
  serverPath: telegramServerPath(),
38607
38896
  // Task 2368 — the role branch lives in its own module so it has a test that
@@ -38612,6 +38901,7 @@ var telegramGateway = new TelegramGateway({
38612
38901
  managerSpawn,
38613
38902
  resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
38614
38903
  startFileFollower: startTelegramNativeFileFollower,
38904
+ fanTargets: telegramFanTargets,
38615
38905
  followers: telegramFileFollowers
38616
38906
  })
38617
38907
  });
@@ -38670,9 +38960,9 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
38670
38960
  const entries2 = listBotEntries(a.config.telegram);
38671
38961
  for (const bot of readChannelAdmins(a.accountDir, "telegram").byBot) {
38672
38962
  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 });
38963
+ const entry2 = entries2.find((e) => e.id === bot.botId);
38964
+ if (!entry2) continue;
38965
+ hits.push({ accountId: a.accountId, botId: bot.botId, botToken: entry2.token });
38676
38966
  }
38677
38967
  }
38678
38968
  if (hits.length === 0) return { kind: "none" };
@@ -39052,8 +39342,8 @@ app80.get("/agent-assets/:slug/:filename", (c) => {
39052
39342
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
39053
39343
  return c.text("Not found", 404);
39054
39344
  }
39055
- const filePath = resolve50(account.accountDir, "agents", slug, "assets", filename);
39056
- const expectedDir = resolve50(account.accountDir, "agents", slug, "assets");
39345
+ const filePath = resolve51(account.accountDir, "agents", slug, "assets", filename);
39346
+ const expectedDir = resolve51(account.accountDir, "agents", slug, "assets");
39057
39347
  if (!filePath.startsWith(expectedDir + "/")) {
39058
39348
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
39059
39349
  return c.text("Forbidden", 403);
@@ -39065,7 +39355,7 @@ app80.get("/agent-assets/:slug/:filename", (c) => {
39065
39355
  const ext = "." + filename.split(".").pop()?.toLowerCase();
39066
39356
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
39067
39357
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
39068
- const body = readFileSync54(filePath);
39358
+ const body = readFileSync55(filePath);
39069
39359
  return c.body(body, 200, {
39070
39360
  "Content-Type": contentType,
39071
39361
  "Cache-Control": "public, max-age=3600"
@@ -39082,8 +39372,8 @@ app80.get("/generated/:filename", (c) => {
39082
39372
  console.error(`[generated] serve file=${filename} status=404`);
39083
39373
  return c.text("Not found", 404);
39084
39374
  }
39085
- const filePath = resolve50(account.accountDir, "generated", filename);
39086
- const expectedDir = resolve50(account.accountDir, "generated");
39375
+ const filePath = resolve51(account.accountDir, "generated", filename);
39376
+ const expectedDir = resolve51(account.accountDir, "generated");
39087
39377
  if (!filePath.startsWith(expectedDir + "/")) {
39088
39378
  console.error(`[generated] serve file=${filename} status=403`);
39089
39379
  return c.text("Forbidden", 403);
@@ -39095,7 +39385,7 @@ app80.get("/generated/:filename", (c) => {
39095
39385
  const ext = "." + filename.split(".").pop()?.toLowerCase();
39096
39386
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
39097
39387
  console.log(`[generated] serve file=${filename} status=200`);
39098
- const body = readFileSync54(filePath);
39388
+ const body = readFileSync55(filePath);
39099
39389
  return c.body(body, 200, {
39100
39390
  "Content-Type": contentType,
39101
39391
  "Cache-Control": "public, max-age=86400"
@@ -39110,7 +39400,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
39110
39400
  var brandIconPath = "/brand/maxy-monochrome.png";
39111
39401
  if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
39112
39402
  try {
39113
- const fullBrand = JSON.parse(readFileSync54(BRAND_JSON_PATH, "utf-8"));
39403
+ const fullBrand = JSON.parse(readFileSync55(BRAND_JSON_PATH, "utf-8"));
39114
39404
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
39115
39405
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
39116
39406
  } catch {
@@ -39137,11 +39427,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
39137
39427
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
39138
39428
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
39139
39429
  var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
39140
- (p) => existsSync56(resolve50(process.cwd(), "public", p.replace(/^\//, "")))
39430
+ (p) => existsSync56(resolve51(process.cwd(), "public", p.replace(/^\//, "")))
39141
39431
  );
39142
39432
  var SW_SOURCE = (() => {
39143
39433
  try {
39144
- return readFileSync54(resolve50(process.cwd(), "public", "sw.js"), "utf-8");
39434
+ return readFileSync55(resolve51(process.cwd(), "public", "sw.js"), "utf-8");
39145
39435
  } catch {
39146
39436
  return null;
39147
39437
  }
@@ -39151,7 +39441,7 @@ function readInstalledVersion() {
39151
39441
  if (!PLATFORM_ROOT6) return "unknown";
39152
39442
  const versionFile = join63(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
39153
39443
  if (!existsSync56(versionFile)) return "unknown";
39154
- const content = readFileSync54(versionFile, "utf-8").trim();
39444
+ const content = readFileSync55(versionFile, "utf-8").trim();
39155
39445
  return content || "unknown";
39156
39446
  } catch {
39157
39447
  return "unknown";
@@ -39162,7 +39452,7 @@ var versionScript = `<script>window.__MAXY_VERSION__=${JSON.stringify(INSTALLED_
39162
39452
  function cachedHtml(file) {
39163
39453
  let html = htmlCache.get(file);
39164
39454
  if (!html) {
39165
- html = readFileSync54(resolve50(process.cwd(), "public", file), "utf-8");
39455
+ html = readFileSync55(resolve51(process.cwd(), "public", file), "utf-8");
39166
39456
  const productNameEsc = escapeHtml(BRAND.productName);
39167
39457
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
39168
39458
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -39186,7 +39476,7 @@ function loadBrandingCache(agentSlug) {
39186
39476
  if (!accountId) return null;
39187
39477
  const cachePath = join63(configDir, "branding-cache", accountId, `${agentSlug}.json`);
39188
39478
  if (!existsSync56(cachePath)) return null;
39189
- return JSON.parse(readFileSync54(cachePath, "utf-8"));
39479
+ return JSON.parse(readFileSync55(cachePath, "utf-8"));
39190
39480
  } catch {
39191
39481
  return null;
39192
39482
  }
@@ -39281,7 +39571,7 @@ app80.use("/vnc-popout.html", logViewerFetch);
39281
39571
  app80.get("/vnc-popout.html", (c) => {
39282
39572
  let html = htmlCache.get("vnc-popout.html");
39283
39573
  if (!html) {
39284
- html = readFileSync54(resolve50(process.cwd(), "public", "vnc-popout.html"), "utf-8");
39574
+ html = readFileSync55(resolve51(process.cwd(), "public", "vnc-popout.html"), "utf-8");
39285
39575
  const name = escapeHtml(BRAND.productName);
39286
39576
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
39287
39577
  html = html.replace("</head>", ` ${brandScript}
@@ -39441,7 +39731,7 @@ var httpServer = serve({ fetch: app80.fetch, port, hostname });
39441
39731
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39442
39732
  {
39443
39733
  const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39444
- const reconcileScript = resolve50(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
39734
+ const reconcileScript = resolve51(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
39445
39735
  const runReconcile = (ctx) => {
39446
39736
  if (!existsSync56(reconcileScript)) return;
39447
39737
  try {
@@ -39482,7 +39772,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39482
39772
  {
39483
39773
  const DREAM_CYCLE_SWEEP_INTERVAL_MS = 36e5;
39484
39774
  const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39485
- const dreamSweepScript = resolve50(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
39775
+ const dreamSweepScript = resolve51(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
39486
39776
  registerLoop({
39487
39777
  name: "dream-cycle-sweep",
39488
39778
  intervalMs: DREAM_CYCLE_SWEEP_INTERVAL_MS,
@@ -39506,7 +39796,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39506
39796
  }
39507
39797
  {
39508
39798
  const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39509
- const outlookScript = resolve50(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
39799
+ const outlookScript = resolve51(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
39510
39800
  const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
39511
39801
  const runOutlookComplete = (ctx) => {
39512
39802
  if (!existsSync56(outlookScript)) return;
@@ -39532,7 +39822,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39532
39822
  }
39533
39823
  {
39534
39824
  const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39535
- const strandedAccountsDir = resolve50(auditRoot, "..", "data/accounts");
39825
+ const strandedAccountsDir = resolve51(auditRoot, "..", "data/accounts");
39536
39826
  const STRANDED_AUDIT_INTERVAL_MS = 3e5;
39537
39827
  const STRANDED_AGE_MS = 16 * 6e4;
39538
39828
  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;
@@ -39542,7 +39832,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39542
39832
  const now = Date.now();
39543
39833
  for (const name of readdirSync32(strandedAccountsDir)) {
39544
39834
  if (!STRANDED_UUID_RE.test(name)) continue;
39545
- const pendingPath2 = resolve50(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
39835
+ const pendingPath2 = resolve51(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
39546
39836
  if (!existsSync56(pendingPath2)) continue;
39547
39837
  const ageMs2 = now - statSync30(pendingPath2).mtimeMs;
39548
39838
  if (ageMs2 > STRANDED_AGE_MS) {
@@ -39562,7 +39852,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39562
39852
  }
39563
39853
  {
39564
39854
  const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39565
- const googleAccountsDir = resolve50(googleRoot, "..", "data/accounts");
39855
+ const googleAccountsDir = resolve51(googleRoot, "..", "data/accounts");
39566
39856
  const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
39567
39857
  const runGooglePendingAuditSafe = () => {
39568
39858
  try {
@@ -39577,7 +39867,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39577
39867
  firstRunDelayMs: 27e3,
39578
39868
  run: runGooglePendingAuditSafe
39579
39869
  });
39580
- const googleAuditScript = resolve50(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
39870
+ const googleAuditScript = resolve51(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
39581
39871
  const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
39582
39872
  const runGoogleAccountAudit = (ctx) => {
39583
39873
  if (!existsSync56(googleAuditScript)) return;
@@ -39640,7 +39930,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39640
39930
  for (const o of migrateTelegramTwoSlot({
39641
39931
  accounts: listValidAccounts().map((a) => ({ accountId: a.accountId, accountDir: a.accountDir })),
39642
39932
  secretPathFor: secretPath,
39643
- legacySecretPathFor: (botType, accountId) => resolve50(
39933
+ legacySecretPathFor: (botType, accountId) => resolve51(
39644
39934
  homedir5(),
39645
39935
  configDirName2(),
39646
39936
  `${botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret"}.${accountId}`
@@ -39808,7 +40098,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39808
40098
  }
39809
40099
  {
39810
40100
  const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39811
- const publishScript = resolve50(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
40101
+ const publishScript = resolve51(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
39812
40102
  const PUBLISH_INTERVAL_MS = 3e5;
39813
40103
  const bookingAccounts = () => {
39814
40104
  try {
@@ -39838,21 +40128,21 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
39838
40128
  }
39839
40129
  };
39840
40130
  const auditSnapshotAge = (account) => {
39841
- const availPath = resolve50(account.accountDir, "calendar-availability.json");
40131
+ const availPath = resolve51(account.accountDir, "calendar-availability.json");
39842
40132
  let hasBookingSite = false;
39843
40133
  try {
39844
40134
  if (existsSync56(availPath)) {
39845
- const cfg = JSON.parse(readFileSync54(availPath, "utf-8"));
40135
+ const cfg = JSON.parse(readFileSync55(availPath, "utf-8"));
39846
40136
  hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
39847
40137
  }
39848
40138
  } catch {
39849
40139
  }
39850
40140
  if (!hasBookingSite) return;
39851
- const statePath = resolve50(account.accountDir, "state", "booking-availability", "last-publish.json");
40141
+ const statePath = resolve51(account.accountDir, "state", "booking-availability", "last-publish.json");
39852
40142
  let lastSuccessAt = null;
39853
40143
  if (existsSync56(statePath)) {
39854
40144
  try {
39855
- const rec = JSON.parse(readFileSync54(statePath, "utf-8"));
40145
+ const rec = JSON.parse(readFileSync55(statePath, "utf-8"));
39856
40146
  lastSuccessAt = rec.lastSuccessAt ?? null;
39857
40147
  } catch {
39858
40148
  console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
@@ -39888,7 +40178,7 @@ startPlannerDivergence();
39888
40178
  startDispatchFieldCensus(() => getSession());
39889
40179
  {
39890
40180
  const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..");
39891
- const auditScript = resolve50(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
40181
+ const auditScript = resolve51(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
39892
40182
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
39893
40183
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
39894
40184
  const runConnectorAudit = (ctx) => {
@@ -40018,7 +40308,7 @@ async function runAdminUserReconcileTick() {
40018
40308
  console.error("[adminuser-self-heal] skip reason=no-users-file");
40019
40309
  return;
40020
40310
  }
40021
- const usersRaw = readFileSync54(USERS_FILE, "utf-8").trim();
40311
+ const usersRaw = readFileSync55(USERS_FILE, "utf-8").trim();
40022
40312
  if (!usersRaw) {
40023
40313
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
40024
40314
  return;
@@ -40165,7 +40455,7 @@ registerLoop({
40165
40455
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
40166
40456
  function countUsersRows() {
40167
40457
  if (!existsSync56(USERS_FILE)) return 0;
40168
- const raw = readFileSync54(USERS_FILE, "utf-8").trim();
40458
+ const raw = readFileSync55(USERS_FILE, "utf-8").trim();
40169
40459
  if (!raw) return 0;
40170
40460
  const users = JSON.parse(raw);
40171
40461
  return users.filter((u) => typeof u.userId === "string").length;
@@ -40333,7 +40623,7 @@ if (bootAccountConfig?.whatsapp) {
40333
40623
  }
40334
40624
  init({
40335
40625
  configDir: configDirForWhatsApp,
40336
- platformRoot: resolve50(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..")),
40626
+ platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ?? join63(__dirname, "..")),
40337
40627
  accountConfig: bootAccountConfig,
40338
40628
  // Task 2074 — enrich with the sub-account name and fan out to every open
40339
40629
  // admin SSE stream. Fire-and-forget: the manager's persist path must not