@rubytech/create-sitedesk-code 0.1.521 → 0.1.523

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 (416) hide show
  1. package/dist/index.js +17 -3
  2. package/package.json +1 -1
  3. package/payload/platform/lib/account-schema-regions/package-lock.json +12 -0
  4. package/payload/platform/lib/active-rules/package-lock.json +12 -0
  5. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/graph-style/dist/index.js +28 -0
  7. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  8. package/payload/platform/lib/graph-style/src/__tests__/caption-coverage.test.ts +7 -0
  9. package/payload/platform/lib/graph-style/src/index.ts +24 -0
  10. package/payload/platform/lib/graph-write/dist/__tests__/merge-on.test.d.ts.map +1 -0
  11. package/payload/platform/lib/graph-write/dist/__tests__/merge-on.test.js.map +1 -0
  12. package/payload/platform/lib/graph-write/dist/index.d.ts +16 -0
  13. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  14. package/payload/platform/lib/graph-write/dist/index.js +83 -6
  15. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  16. package/payload/platform/lib/graph-write/src/index.ts +117 -10
  17. package/payload/platform/lib/mcp-lifeline/package-lock.json +12 -0
  18. package/payload/platform/lib/routine-templates/dist/index.d.ts +151 -0
  19. package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -0
  20. package/payload/platform/lib/routine-templates/dist/index.js +207 -0
  21. package/payload/platform/lib/routine-templates/dist/index.js.map +1 -0
  22. package/payload/platform/lib/routine-templates/dist/roster.d.ts +57 -0
  23. package/payload/platform/lib/routine-templates/dist/roster.d.ts.map +1 -0
  24. package/payload/platform/lib/routine-templates/dist/roster.js +182 -0
  25. package/payload/platform/lib/routine-templates/dist/roster.js.map +1 -0
  26. package/payload/platform/lib/routine-templates/dist/seed-cli.d.ts +2 -0
  27. package/payload/platform/lib/routine-templates/dist/seed-cli.d.ts.map +1 -0
  28. package/payload/platform/lib/routine-templates/dist/seed-cli.js +64 -0
  29. package/payload/platform/lib/routine-templates/dist/seed-cli.js.map +1 -0
  30. package/payload/platform/lib/routine-templates/package.json +6 -0
  31. package/payload/platform/lib/routine-templates/src/__tests__/roster.test.ts +170 -0
  32. package/payload/platform/lib/routine-templates/src/__tests__/seed.test.ts +298 -0
  33. package/payload/platform/lib/routine-templates/src/index.ts +299 -0
  34. package/payload/platform/lib/routine-templates/src/roster.ts +206 -0
  35. package/payload/platform/lib/routine-templates/src/seed-cli.ts +65 -0
  36. package/payload/platform/lib/routine-templates/tsconfig.json +9 -0
  37. package/payload/platform/lib/routine-templates/vitest.config.ts +9 -0
  38. package/payload/platform/neo4j/edge-annotations.json +2 -2
  39. package/payload/platform/neo4j/schema.cypher +25 -1
  40. package/payload/platform/package.json +3 -2
  41. package/payload/platform/plugins/admin/PLUGIN.md +2 -2
  42. package/payload/platform/plugins/admin/mcp/dist/index.js +39 -1
  43. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  44. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +319 -66
  45. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
  46. package/payload/platform/plugins/docs/references/admin-ui.md +275 -59
  47. package/payload/platform/plugins/docs/references/internals.md +4 -0
  48. package/payload/platform/plugins/docs/references/memory-guide.md +11 -1
  49. package/payload/platform/plugins/docs/references/outlook-guide.md +27 -4
  50. package/payload/platform/plugins/graph/.claude-plugin/plugin.json +1 -1
  51. package/payload/platform/plugins/graph/PLUGIN.md +1 -1
  52. package/payload/platform/plugins/memory/PLUGIN.md +1 -1
  53. package/payload/platform/plugins/memory/mcp/dist/index.js +12 -5
  54. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  55. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-estate-census.test.d.ts +2 -0
  56. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-estate-census.test.d.ts.map +1 -0
  57. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-estate-census.test.js +197 -0
  58. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-estate-census.test.js.map +1 -0
  59. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +55 -9
  60. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
  61. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +96 -0
  62. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
  63. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts +7 -0
  64. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts.map +1 -1
  65. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-estate-census.d.ts +29 -0
  66. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-estate-census.d.ts.map +1 -0
  67. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-estate-census.js +126 -0
  68. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-estate-census.js.map +1 -0
  69. package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js +7 -4
  70. package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js.map +1 -1
  71. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts +1 -0
  72. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts.map +1 -1
  73. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js +18 -1
  74. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js.map +1 -1
  75. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +7 -0
  76. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  77. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +4 -3
  78. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  79. package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
  80. package/payload/platform/plugins/memory/references/schema-estate-agent.md +2 -2
  81. package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +4 -4
  82. package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
  83. package/payload/platform/plugins/outlook/PLUGIN.md +9 -1
  84. package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-resolve.test.d.ts +2 -0
  85. package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-resolve.test.d.ts.map +1 -0
  86. package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-resolve.test.js +151 -0
  87. package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-resolve.test.js.map +1 -0
  88. package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-store.test.d.ts +2 -0
  89. package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-store.test.d.ts.map +1 -0
  90. package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-store.test.js +151 -0
  91. package/payload/platform/plugins/outlook/mcp/dist/__tests__/delegated-store.test.js.map +1 -0
  92. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +38 -4
  93. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
  94. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-prefix-wiring.test.d.ts +2 -0
  95. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-prefix-wiring.test.d.ts.map +1 -0
  96. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-prefix-wiring.test.js +146 -0
  97. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-prefix-wiring.test.js.map +1 -0
  98. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list-delegated.test.d.ts +2 -0
  99. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list-delegated.test.d.ts.map +1 -0
  100. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list-delegated.test.js +120 -0
  101. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list-delegated.test.js.map +1 -0
  102. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js +8 -8
  103. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js.map +1 -1
  104. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-path-prefix.test.d.ts +2 -0
  105. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-path-prefix.test.d.ts.map +1 -0
  106. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-path-prefix.test.js +27 -0
  107. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-path-prefix.test.js.map +1 -0
  108. package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mail-only-guard.test.d.ts +2 -0
  109. package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mail-only-guard.test.d.ts.map +1 -0
  110. package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mail-only-guard.test.js +70 -0
  111. package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mail-only-guard.test.js.map +1 -0
  112. package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mailbox-add.test.d.ts +2 -0
  113. package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mailbox-add.test.d.ts.map +1 -0
  114. package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mailbox-add.test.js +152 -0
  115. package/payload/platform/plugins/outlook/mcp/dist/__tests__/shared-mailbox-add.test.js.map +1 -0
  116. package/payload/platform/plugins/outlook/mcp/dist/auth/delegated-store.d.ts +57 -0
  117. package/payload/platform/plugins/outlook/mcp/dist/auth/delegated-store.d.ts.map +1 -0
  118. package/payload/platform/plugins/outlook/mcp/dist/auth/delegated-store.js +163 -0
  119. package/payload/platform/plugins/outlook/mcp/dist/auth/delegated-store.js.map +1 -0
  120. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +12 -0
  121. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
  122. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +21 -1
  123. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
  124. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts +35 -4
  125. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts.map +1 -1
  126. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js +92 -10
  127. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js.map +1 -1
  128. package/payload/platform/plugins/outlook/mcp/dist/index.js +106 -3
  129. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  130. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +49 -0
  131. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -1
  132. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +126 -8
  133. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
  134. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts +13 -0
  135. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts.map +1 -1
  136. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js +9 -0
  137. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js.map +1 -1
  138. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts +42 -8
  139. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts.map +1 -1
  140. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js +59 -19
  141. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js.map +1 -1
  142. package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-add.d.ts +47 -0
  143. package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-add.d.ts.map +1 -0
  144. package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-add.js +67 -0
  145. package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-add.js.map +1 -0
  146. package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-remove.d.ts +12 -0
  147. package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-remove.d.ts.map +1 -0
  148. package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-remove.js +18 -0
  149. package/payload/platform/plugins/outlook/mcp/dist/tools/shared-mailbox-remove.js.map +1 -0
  150. package/payload/platform/plugins/outlook/references/auth.md +15 -2
  151. package/payload/platform/plugins/outlook/references/graph-surfaces.md +8 -0
  152. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +21 -1
  153. package/payload/platform/plugins/quickbooks/PLUGIN.md +10 -10
  154. package/payload/platform/plugins/scheduling/PLUGIN.md +49 -1
  155. package/payload/platform/plugins/scheduling/mcp/dist/index.js +48 -0
  156. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  157. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/notes-cap.test.d.ts +2 -0
  158. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/notes-cap.test.d.ts.map +1 -0
  159. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/notes-cap.test.js +78 -0
  160. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/notes-cap.test.js.map +1 -0
  161. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.d.ts +2 -0
  162. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.d.ts.map +1 -0
  163. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.js +457 -0
  164. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.js.map +1 -0
  165. package/payload/platform/plugins/scheduling/mcp/dist/lib/notes-cap.d.ts +35 -0
  166. package/payload/platform/plugins/scheduling/mcp/dist/lib/notes-cap.d.ts.map +1 -0
  167. package/payload/platform/plugins/scheduling/mcp/dist/lib/notes-cap.js +39 -0
  168. package/payload/platform/plugins/scheduling/mcp/dist/lib/notes-cap.js.map +1 -0
  169. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts +175 -0
  170. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts.map +1 -0
  171. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js +533 -0
  172. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js.map +1 -0
  173. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +123 -9
  174. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -1
  175. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.d.ts +2 -0
  176. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.d.ts.map +1 -0
  177. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js +189 -0
  178. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js.map +1 -0
  179. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.d.ts +2 -0
  180. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.d.ts.map +1 -0
  181. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.js +95 -0
  182. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.js.map +1 -0
  183. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/sweep-excludes-suspended.test.d.ts +2 -0
  184. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/sweep-excludes-suspended.test.d.ts.map +1 -0
  185. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/sweep-excludes-suspended.test.js +114 -0
  186. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/sweep-excludes-suspended.test.js.map +1 -0
  187. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +109 -2
  188. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  189. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +340 -9
  190. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  191. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts +9 -1
  192. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts.map +1 -1
  193. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +210 -45
  194. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  195. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +43 -1
  196. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  197. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js +55 -7
  198. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js.map +1 -1
  199. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list.test.js +45 -1
  200. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list.test.js.map +1 -1
  201. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-runs.test.d.ts +2 -0
  202. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-runs.test.d.ts.map +1 -0
  203. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-runs.test.js +96 -0
  204. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-runs.test.js.map +1 -0
  205. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts.map +1 -1
  206. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js +2 -1
  207. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js.map +1 -1
  208. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +5 -0
  209. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
  210. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +13 -0
  211. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
  212. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts +4 -0
  213. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts.map +1 -1
  214. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js +24 -3
  215. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js.map +1 -1
  216. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-runs.d.ts +33 -0
  217. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-runs.d.ts.map +1 -0
  218. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-runs.js +51 -0
  219. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-runs.js.map +1 -0
  220. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  221. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +12 -2
  222. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  223. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +30 -2
  224. package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +1 -1
  225. package/payload/platform/scripts/check-specialist-tool-surface.mjs +92 -16
  226. package/payload/platform/scripts/seed-neo4j.sh +41 -0
  227. package/payload/platform/scripts/verify-skill-tool-surface.sh +91 -2
  228. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  229. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +3 -0
  230. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  231. package/payload/platform/templates/specialists/agents/librarian.md +1 -1
  232. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  233. package/payload/server/{chunk-AZLT4LVO.js → chunk-VUXKXOJU.js} +755 -335
  234. package/payload/server/{chunk-UX2FRNO7.js → chunk-WT2PFGY4.js} +37 -265
  235. package/payload/server/{chunk-PXNY2TAP.js → chunk-XCR5D2G3.js} +239 -10
  236. package/payload/server/{manager-CQBSUJXR.js → manager-4TJK3YOY.js} +4 -2
  237. package/payload/server/maxy-edge.js +2 -2
  238. package/payload/server/public/activity.html +5 -5
  239. package/payload/server/public/agents.html +4 -4
  240. package/payload/server/public/assets/{AdminLoginScreens-Dar_MMJm.js → AdminLoginScreens-uknqh2Pm.js} +1 -1
  241. package/payload/server/public/assets/AdminLoginScreens-uknqh2Pm.js.br +0 -0
  242. package/payload/server/public/assets/AdminLoginScreens-uknqh2Pm.js.gz +0 -0
  243. package/payload/server/public/assets/AdminShell-GqtMImyQ.js +2 -0
  244. package/payload/server/public/assets/AdminShell-GqtMImyQ.js.br +0 -0
  245. package/payload/server/public/assets/AdminShell-GqtMImyQ.js.gz +0 -0
  246. package/payload/server/public/assets/{activity-B1rt4GIy.js → activity-B4KfMJqO.js} +1 -1
  247. package/payload/server/public/assets/activity-B4KfMJqO.js.br +0 -0
  248. package/payload/server/public/assets/activity-B4KfMJqO.js.gz +0 -0
  249. package/payload/server/public/assets/admin-DxCZeVw0.js +1 -0
  250. package/payload/server/public/assets/admin-DxCZeVw0.js.br +0 -0
  251. package/payload/server/public/assets/admin-DxCZeVw0.js.gz +0 -0
  252. package/payload/server/public/assets/agents-BtwBuO4G.js +1 -0
  253. package/payload/server/public/assets/agents-BtwBuO4G.js.br +0 -0
  254. package/payload/server/public/assets/agents-BtwBuO4G.js.gz +0 -0
  255. package/payload/server/public/assets/{browser-1QSCPl3X.js → browser-B3hSzI3O.js} +1 -1
  256. package/payload/server/public/assets/browser-B3hSzI3O.js.br +0 -0
  257. package/payload/server/public/assets/browser-B3hSzI3O.js.gz +0 -0
  258. package/payload/server/public/assets/{calendar-D009qWVV.js → calendar-BggxXxhS.js} +1 -1
  259. package/payload/server/public/assets/calendar-BggxXxhS.js.br +0 -0
  260. package/payload/server/public/assets/calendar-BggxXxhS.js.gz +0 -0
  261. package/payload/server/public/assets/chat-flivHt0t.js +1 -0
  262. package/payload/server/public/assets/chat-flivHt0t.js.br +0 -0
  263. package/payload/server/public/assets/chat-flivHt0t.js.gz +0 -0
  264. package/payload/server/public/assets/chevron-left-nVRThX7f.js +1 -0
  265. package/payload/server/public/assets/chevron-right-CLqfrkRA.js +1 -0
  266. package/payload/server/public/assets/chevron-right-CLqfrkRA.js.br +0 -0
  267. package/payload/server/public/assets/clock-Dj3SRZKo.js +1 -0
  268. package/payload/server/public/assets/clock-Dj3SRZKo.js.br +0 -0
  269. package/payload/server/public/assets/clock-Dj3SRZKo.js.gz +0 -0
  270. package/payload/server/public/assets/data-Ckjkne54.js +1 -0
  271. package/payload/server/public/assets/data-Ckjkne54.js.br +3 -0
  272. package/payload/server/public/assets/data-Ckjkne54.js.gz +0 -0
  273. package/payload/server/public/assets/{file-text-DwGAfCsd.js → file-text-BKs9eyiw.js} +1 -1
  274. package/payload/server/public/assets/file-text-BKs9eyiw.js.br +0 -0
  275. package/payload/server/public/assets/file-text-BKs9eyiw.js.gz +0 -0
  276. package/payload/server/public/assets/{graph-D-DGlfhW.js → graph-DTi71NaZ.js} +3 -3
  277. package/payload/server/public/assets/graph-DTi71NaZ.js.br +0 -0
  278. package/payload/server/public/assets/graph-DTi71NaZ.js.gz +0 -0
  279. package/payload/server/public/assets/graph-labels-BT3S6e9H.js +1 -0
  280. package/payload/server/public/assets/graph-labels-BT3S6e9H.js.br +0 -0
  281. package/payload/server/public/assets/graph-labels-BT3S6e9H.js.gz +0 -0
  282. package/payload/server/public/assets/{maximize-2-C_-KapxH.js → maximize-2-zC94tsxY.js} +1 -1
  283. package/payload/server/public/assets/maximize-2-zC94tsxY.js.br +0 -0
  284. package/payload/server/public/assets/maximize-2-zC94tsxY.js.gz +0 -0
  285. package/payload/server/public/assets/operator-BtWNEMh-.js +1 -0
  286. package/payload/server/public/assets/operator-BtWNEMh-.js.br +0 -0
  287. package/payload/server/public/assets/operator-BtWNEMh-.js.gz +0 -0
  288. package/payload/server/public/assets/page-BFm91p0e.js +1 -0
  289. package/payload/server/public/assets/page-BFm91p0e.js.br +0 -0
  290. package/payload/server/public/assets/page-BFm91p0e.js.gz +0 -0
  291. package/payload/server/public/assets/{page-E74MYB18.js → page-Ccl0FazM.js} +3 -3
  292. package/payload/server/public/assets/page-Ccl0FazM.js.br +0 -0
  293. package/payload/server/public/assets/page-Ccl0FazM.js.gz +0 -0
  294. package/payload/server/public/assets/{public-CI6K1sNW.js → public-BpC6cn9S.js} +1 -1
  295. package/payload/server/public/assets/public-BpC6cn9S.js.br +0 -0
  296. package/payload/server/public/assets/public-BpC6cn9S.js.gz +0 -0
  297. package/payload/server/public/assets/{rotate-ccw-PWu_QGrD.js → rotate-ccw-BNcqbHkZ.js} +1 -1
  298. package/payload/server/public/assets/rotate-ccw-BNcqbHkZ.js.br +0 -0
  299. package/payload/server/public/assets/rotate-ccw-BNcqbHkZ.js.gz +0 -0
  300. package/payload/server/public/assets/routines-D5iia-XG.js +1 -0
  301. package/payload/server/public/assets/routines-D5iia-XG.js.br +0 -0
  302. package/payload/server/public/assets/routines-D5iia-XG.js.gz +0 -0
  303. package/payload/server/public/assets/{skills-g8HvunPA.js → skills-COLAw_jt.js} +1 -1
  304. package/payload/server/public/assets/skills-COLAw_jt.js.br +0 -0
  305. package/payload/server/public/assets/skills-COLAw_jt.js.gz +0 -0
  306. package/payload/server/public/assets/tasks-CZDQI4nB.js +1 -0
  307. package/payload/server/public/assets/tasks-CZDQI4nB.js.br +0 -0
  308. package/payload/server/public/assets/tasks-CZDQI4nB.js.gz +0 -0
  309. package/payload/server/public/assets/{time-entry-format-BXWuQ6Sw.js → time-entry-format-laTFzawg.js} +1 -1
  310. package/payload/server/public/assets/time-entry-format-laTFzawg.js.br +0 -0
  311. package/payload/server/public/assets/time-entry-format-laTFzawg.js.gz +0 -0
  312. package/payload/server/public/assets/{triangle-alert-BNshRC13.js → triangle-alert-DrwqhEog.js} +1 -1
  313. package/payload/server/public/assets/triangle-alert-DrwqhEog.js.br +0 -0
  314. package/payload/server/public/assets/triangle-alert-DrwqhEog.js.gz +0 -0
  315. package/payload/server/public/assets/{useCopyFeedback-CwTZ9DqH.js → useCopyFeedback-Cxme5GK6.js} +1 -1
  316. package/payload/server/public/assets/useCopyFeedback-Cxme5GK6.js.br +0 -0
  317. package/payload/server/public/assets/useCopyFeedback-Cxme5GK6.js.gz +0 -0
  318. package/payload/server/public/assets/useSubAccountSwitcher-B-K8y5a4.css +1 -0
  319. package/payload/server/public/assets/useSubAccountSwitcher-B-K8y5a4.css.br +0 -0
  320. package/payload/server/public/assets/useSubAccountSwitcher-B-K8y5a4.css.gz +0 -0
  321. package/payload/server/public/assets/useSubAccountSwitcher-C_1E11Dv.js +14 -0
  322. package/payload/server/public/assets/useSubAccountSwitcher-C_1E11Dv.js.br +0 -0
  323. package/payload/server/public/assets/useSubAccountSwitcher-C_1E11Dv.js.gz +0 -0
  324. package/payload/server/public/assets/{useVoiceRecorder-BGqV5R6u.js → useVoiceRecorder-DUBpVa2T.js} +1 -1
  325. package/payload/server/public/assets/useVoiceRecorder-DUBpVa2T.js.br +0 -0
  326. package/payload/server/public/assets/useVoiceRecorder-DUBpVa2T.js.gz +0 -0
  327. package/payload/server/public/assets/{wrench-DQ045Pyk.js → wrench-DWuxPnzF.js} +1 -1
  328. package/payload/server/public/assets/wrench-DWuxPnzF.js.br +0 -0
  329. package/payload/server/public/assets/wrench-DWuxPnzF.js.gz +0 -0
  330. package/payload/server/public/browser.html +4 -4
  331. package/payload/server/public/calendar.html +7 -7
  332. package/payload/server/public/chat.html +13 -13
  333. package/payload/server/public/data.html +11 -11
  334. package/payload/server/public/graph.html +9 -9
  335. package/payload/server/public/index.html +14 -14
  336. package/payload/server/public/operator.html +14 -14
  337. package/payload/server/public/public.html +13 -13
  338. package/payload/server/public/routines.html +6 -6
  339. package/payload/server/public/skills.html +5 -5
  340. package/payload/server/public/tasks.html +6 -6
  341. package/payload/server/server.js +6631 -5690
  342. package/payload/server/public/assets/AdminLoginScreens-Dar_MMJm.js.br +0 -0
  343. package/payload/server/public/assets/AdminLoginScreens-Dar_MMJm.js.gz +0 -0
  344. package/payload/server/public/assets/AdminShell-CpaI2F_p.js +0 -2
  345. package/payload/server/public/assets/AdminShell-CpaI2F_p.js.br +0 -0
  346. package/payload/server/public/assets/AdminShell-CpaI2F_p.js.gz +0 -0
  347. package/payload/server/public/assets/activity-B1rt4GIy.js.br +0 -0
  348. package/payload/server/public/assets/activity-B1rt4GIy.js.gz +0 -0
  349. package/payload/server/public/assets/admin-DE3lLNje.js +0 -1
  350. package/payload/server/public/assets/admin-DE3lLNje.js.br +0 -0
  351. package/payload/server/public/assets/admin-DE3lLNje.js.gz +0 -0
  352. package/payload/server/public/assets/agents-BXq8woVf.js +0 -1
  353. package/payload/server/public/assets/agents-BXq8woVf.js.br +0 -0
  354. package/payload/server/public/assets/agents-BXq8woVf.js.gz +0 -0
  355. package/payload/server/public/assets/browser-1QSCPl3X.js.br +0 -0
  356. package/payload/server/public/assets/browser-1QSCPl3X.js.gz +0 -0
  357. package/payload/server/public/assets/calendar-D009qWVV.js.br +0 -0
  358. package/payload/server/public/assets/calendar-D009qWVV.js.gz +0 -0
  359. package/payload/server/public/assets/chat-CUg1fTqJ.js +0 -1
  360. package/payload/server/public/assets/chat-CUg1fTqJ.js.br +0 -1
  361. package/payload/server/public/assets/chat-CUg1fTqJ.js.gz +0 -0
  362. package/payload/server/public/assets/chevron-left-C2JEy7uU.js +0 -1
  363. package/payload/server/public/assets/chevron-left-C2JEy7uU.js.br +0 -0
  364. package/payload/server/public/assets/chevron-right-DWa047Ys.js +0 -1
  365. package/payload/server/public/assets/chevron-right-DWa047Ys.js.br +0 -0
  366. package/payload/server/public/assets/clock-C3OqfCnn.js +0 -1
  367. package/payload/server/public/assets/clock-C3OqfCnn.js.br +0 -0
  368. package/payload/server/public/assets/clock-C3OqfCnn.js.gz +0 -0
  369. package/payload/server/public/assets/data-Bu_yCScC.js +0 -1
  370. package/payload/server/public/assets/data-Bu_yCScC.js.br +0 -0
  371. package/payload/server/public/assets/data-Bu_yCScC.js.gz +0 -0
  372. package/payload/server/public/assets/file-text-DwGAfCsd.js.br +0 -0
  373. package/payload/server/public/assets/file-text-DwGAfCsd.js.gz +0 -0
  374. package/payload/server/public/assets/graph-D-DGlfhW.js.br +0 -0
  375. package/payload/server/public/assets/graph-D-DGlfhW.js.gz +0 -0
  376. package/payload/server/public/assets/graph-labels-BbX3fXzl.js +0 -1
  377. package/payload/server/public/assets/graph-labels-BbX3fXzl.js.br +0 -0
  378. package/payload/server/public/assets/graph-labels-BbX3fXzl.js.gz +0 -0
  379. package/payload/server/public/assets/maximize-2-C_-KapxH.js.br +0 -0
  380. package/payload/server/public/assets/maximize-2-C_-KapxH.js.gz +0 -0
  381. package/payload/server/public/assets/operator-CFrZVcv6.js +0 -1
  382. package/payload/server/public/assets/operator-CFrZVcv6.js.br +0 -0
  383. package/payload/server/public/assets/operator-CFrZVcv6.js.gz +0 -0
  384. package/payload/server/public/assets/page-CHt62f1_.js +0 -1
  385. package/payload/server/public/assets/page-CHt62f1_.js.br +0 -0
  386. package/payload/server/public/assets/page-CHt62f1_.js.gz +0 -0
  387. package/payload/server/public/assets/page-E74MYB18.js.br +0 -0
  388. package/payload/server/public/assets/page-E74MYB18.js.gz +0 -0
  389. package/payload/server/public/assets/public-CI6K1sNW.js.br +0 -0
  390. package/payload/server/public/assets/public-CI6K1sNW.js.gz +0 -0
  391. package/payload/server/public/assets/rotate-ccw-PWu_QGrD.js.br +0 -0
  392. package/payload/server/public/assets/rotate-ccw-PWu_QGrD.js.gz +0 -0
  393. package/payload/server/public/assets/routines-Cp3_XS7S.js +0 -1
  394. package/payload/server/public/assets/routines-Cp3_XS7S.js.br +0 -0
  395. package/payload/server/public/assets/routines-Cp3_XS7S.js.gz +0 -0
  396. package/payload/server/public/assets/skills-g8HvunPA.js.br +0 -0
  397. package/payload/server/public/assets/skills-g8HvunPA.js.gz +0 -0
  398. package/payload/server/public/assets/tasks-CMC_jDVm.js +0 -1
  399. package/payload/server/public/assets/tasks-CMC_jDVm.js.br +0 -0
  400. package/payload/server/public/assets/tasks-CMC_jDVm.js.gz +0 -0
  401. package/payload/server/public/assets/time-entry-format-BXWuQ6Sw.js.br +0 -2
  402. package/payload/server/public/assets/time-entry-format-BXWuQ6Sw.js.gz +0 -0
  403. package/payload/server/public/assets/triangle-alert-BNshRC13.js.br +0 -0
  404. package/payload/server/public/assets/triangle-alert-BNshRC13.js.gz +0 -0
  405. package/payload/server/public/assets/useCopyFeedback-CwTZ9DqH.js.br +0 -0
  406. package/payload/server/public/assets/useCopyFeedback-CwTZ9DqH.js.gz +0 -0
  407. package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js +0 -14
  408. package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js.br +0 -0
  409. package/payload/server/public/assets/useSubAccountSwitcher-BuUYMDWl.js.gz +0 -0
  410. package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css +0 -1
  411. package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css.br +0 -0
  412. package/payload/server/public/assets/useSubAccountSwitcher-dzDnmWq9.css.gz +0 -0
  413. package/payload/server/public/assets/useVoiceRecorder-BGqV5R6u.js.br +0 -0
  414. package/payload/server/public/assets/useVoiceRecorder-BGqV5R6u.js.gz +0 -0
  415. package/payload/server/public/assets/wrench-DQ045Pyk.js.br +0 -0
  416. package/payload/server/public/assets/wrench-DQ045Pyk.js.gz +0 -0
@@ -6,8 +6,9 @@ import {
6
6
  ensureConversation,
7
7
  getSession,
8
8
  getUserTimezone,
9
- isValidAccountId
10
- } from "./chunk-PXNY2TAP.js";
9
+ isValidAccountId,
10
+ registerLoop
11
+ } from "./chunk-XCR5D2G3.js";
11
12
 
12
13
  // app/lib/whatsapp/schema.ts
13
14
  import { z } from "zod";
@@ -257,7 +258,6 @@ import { inspect } from "util";
257
258
  import {
258
259
  Browsers,
259
260
  DisconnectReason,
260
- fetchLatestBaileysVersion,
261
261
  makeCacheableSignalKeyStore,
262
262
  makeWASocket,
263
263
  useMultiFileAuthState
@@ -318,6 +318,18 @@ function resolveDebugWindow(authDir, nowMs = Date.now()) {
318
318
  return state;
319
319
  }
320
320
 
321
+ // app/lib/whatsapp/companion-reg.ts
322
+ var PRIMARY_IDENTITY_PUB = "primary_identity_pub";
323
+ function classifyCompanionReg(node) {
324
+ const outer = node?.content;
325
+ const child = Array.isArray(outer) ? outer.find((c) => c?.tag === "link_code_companion_reg") : void 0;
326
+ const rawStage = child?.attrs?.stage;
327
+ const stage = typeof rawStage === "string" && rawStage ? rawStage : "absent";
328
+ const inner = child?.content;
329
+ const children = Array.isArray(inner) ? inner.map((c) => c?.tag).filter((t) => typeof t === "string") : [];
330
+ return { stage, children, parseable: children.includes(PRIMARY_IDENTITY_PUB) };
331
+ }
332
+
321
333
  // app/lib/whatsapp/ingest-ledger.ts
322
334
  import { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, readdirSync } from "fs";
323
335
  import { join } from "path";
@@ -332,32 +344,32 @@ function ledgerFilePath(account) {
332
344
  }
333
345
  return join(root, `${account}.jsonl`);
334
346
  }
335
- function appendStanza(entry) {
347
+ function appendStanza(entry2) {
336
348
  try {
337
349
  const row = {
338
- msgId: entry.msgId,
339
- from: entry.from,
340
- type: entry.type,
350
+ msgId: entry2.msgId,
351
+ from: entry2.from,
352
+ type: entry2.type,
341
353
  ts: (/* @__PURE__ */ new Date()).toISOString()
342
354
  };
343
- appendFileSync(ledgerFilePath(entry.account), JSON.stringify(row) + "\n");
355
+ appendFileSync(ledgerFilePath(entry2.account), JSON.stringify(row) + "\n");
344
356
  } catch (err) {
345
357
  console.error(
346
- `${TAG3} FAIL op=append msgId=${entry.msgId} err=${err instanceof Error ? err.message : String(err)}`
358
+ `${TAG3} FAIL op=append msgId=${entry2.msgId} err=${err instanceof Error ? err.message : String(err)}`
347
359
  );
348
360
  }
349
361
  }
350
- function markDeclined(entry) {
362
+ function markDeclined(entry2) {
351
363
  try {
352
364
  const row = {
353
- declined: entry.msgId,
354
- reason: entry.reason,
365
+ declined: entry2.msgId,
366
+ reason: entry2.reason,
355
367
  ts: (/* @__PURE__ */ new Date()).toISOString()
356
368
  };
357
- appendFileSync(ledgerFilePath(entry.account), JSON.stringify(row) + "\n");
369
+ appendFileSync(ledgerFilePath(entry2.account), JSON.stringify(row) + "\n");
358
370
  } catch (err) {
359
371
  console.error(
360
- `${TAG3} FAIL op=decline msgId=${entry.msgId} err=${err instanceof Error ? err.message : String(err)}`
372
+ `${TAG3} FAIL op=decline msgId=${entry2.msgId} err=${err instanceof Error ? err.message : String(err)}`
361
373
  );
362
374
  }
363
375
  }
@@ -375,36 +387,36 @@ function readDeclined(account) {
375
387
  }
376
388
  return out;
377
389
  }
378
- function appendNotifyEligible(entry) {
390
+ function appendNotifyEligible(entry2) {
379
391
  try {
380
392
  const row = {
381
- notifyEligible: entry.msgId,
382
- upsertType: entry.upsertType,
383
- fromMe: entry.fromMe,
384
- allowed: entry.allowed,
385
- reason: entry.reason,
386
- passive: entry.passive,
387
- persistAccount: entry.persistAccount,
393
+ notifyEligible: entry2.msgId,
394
+ upsertType: entry2.upsertType,
395
+ fromMe: entry2.fromMe,
396
+ allowed: entry2.allowed,
397
+ reason: entry2.reason,
398
+ passive: entry2.passive,
399
+ persistAccount: entry2.persistAccount,
388
400
  ts: (/* @__PURE__ */ new Date()).toISOString()
389
401
  };
390
- appendFileSync(ledgerFilePath(entry.account), JSON.stringify(row) + "\n");
402
+ appendFileSync(ledgerFilePath(entry2.account), JSON.stringify(row) + "\n");
391
403
  } catch (err) {
392
404
  console.error(
393
- `${TAG3} FAIL op=append-notify-eligible msgId=${entry.msgId} err=${err instanceof Error ? err.message : String(err)}`
405
+ `${TAG3} FAIL op=append-notify-eligible msgId=${entry2.msgId} err=${err instanceof Error ? err.message : String(err)}`
394
406
  );
395
407
  }
396
408
  }
397
- function appendNotifyPublished(entry) {
409
+ function appendNotifyPublished(entry2) {
398
410
  try {
399
411
  const row = {
400
- notifyPublished: entry.msgId,
401
- subscribers: entry.subscribers,
412
+ notifyPublished: entry2.msgId,
413
+ subscribers: entry2.subscribers,
402
414
  ts: (/* @__PURE__ */ new Date()).toISOString()
403
415
  };
404
- appendFileSync(ledgerFilePath(entry.account), JSON.stringify(row) + "\n");
416
+ appendFileSync(ledgerFilePath(entry2.account), JSON.stringify(row) + "\n");
405
417
  } catch (err) {
406
418
  console.error(
407
- `${TAG3} FAIL op=append-notify-published msgId=${entry.msgId} err=${err instanceof Error ? err.message : String(err)}`
419
+ `${TAG3} FAIL op=append-notify-published msgId=${entry2.msgId} err=${err instanceof Error ? err.message : String(err)}`
408
420
  );
409
421
  }
410
422
  }
@@ -471,8 +483,102 @@ function listLedgerAccounts() {
471
483
  }
472
484
  }
473
485
 
486
+ // app/lib/whatsapp/version-census.ts
487
+ import { DEFAULT_CONNECTION_CONFIG, fetchLatestBaileysVersion } from "@whiskeysockets/baileys";
488
+ var TAG4 = "[whatsapp:version]";
489
+ var BAILEYS_VERSION_URL = "https://raw.githubusercontent.com/WhiskeySockets/Baileys/master/src/Defaults/index.ts";
490
+ var VERSION_RE = /const version = \[(\d+),\s*(\d+),\s*(\d+)\]/;
491
+ var presented = /* @__PURE__ */ new Map();
492
+ var behindSince = null;
493
+ function packagedVersion() {
494
+ return DEFAULT_CONNECTION_CONFIG.version.join(".");
495
+ }
496
+ function messageOf(err) {
497
+ return err instanceof Error ? err.message : String(err);
498
+ }
499
+ var PROBE_TIMEOUT_MS = 5e3;
500
+ async function probeSourceAge(fetchImpl) {
501
+ const none = (probe) => ({ sourceAgeSec: null, edge: null, probe });
502
+ try {
503
+ const res = await fetchImpl(BAILEYS_VERSION_URL, {
504
+ method: "HEAD",
505
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
506
+ });
507
+ if (!res.ok) return none(`http-${res.status}`);
508
+ const header = res.headers.get("age") !== null ? "age" : "source-age";
509
+ const raw = res.headers.get(header);
510
+ const n = raw === null ? Number.NaN : Number.parseInt(raw, 10);
511
+ if (!Number.isFinite(n)) return none("no-header");
512
+ return { sourceAgeSec: n, edge: res.headers.get("x-served-by"), probe: header };
513
+ } catch (err) {
514
+ return none(err instanceof Error && err.name === "TimeoutError" ? "timeout" : "net-error");
515
+ }
516
+ }
517
+ async function resolveWaVersion(opts) {
518
+ const { account } = opts;
519
+ const fetchImpl = opts.fetchImpl ?? fetch;
520
+ const fetchVersion = opts.fetchVersion ?? fetchLatestBaileysVersion;
521
+ const now = opts.now ?? Date.now;
522
+ const timed = (async () => {
523
+ const t0 = now();
524
+ const r2 = await fetchVersion();
525
+ return { r: r2, ms: now() - t0 };
526
+ })();
527
+ const [{ r, ms }, probe] = await Promise.all([timed, probeSourceAge(fetchImpl)]);
528
+ const err = r.error;
529
+ const origin = r.isLatest === false || err !== void 0 ? "packaged-fallback" : "fetched";
530
+ const versionStr = r.version.join(".");
531
+ const packaged = packagedVersion();
532
+ presented.set(account, versionStr);
533
+ console.error(
534
+ `${TAG4} op=resolve account=${account} version=${versionStr} origin=${origin} sourceAgeSec=${probe.sourceAgeSec ?? "unknown"} probe=${probe.probe} edge=${probe.edge ?? "unknown"} packaged=${packaged} matchesPackaged=${versionStr === packaged} fetchMs=${ms} error=${err === void 0 ? "none" : `"${messageOf(err)}"`}`
535
+ );
536
+ return { version: r.version };
537
+ }
538
+ var CENSUS_INTERVAL_MS = 15 * 60 * 1e3;
539
+ async function runVersionDriftCensus(opts = {}) {
540
+ const fetchImpl = opts.fetchImpl ?? fetch;
541
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
542
+ let upstream = null;
543
+ let reason = "net-error";
544
+ try {
545
+ const res = await fetchImpl(BAILEYS_VERSION_URL);
546
+ if (!res.ok) {
547
+ reason = `http-${res.status}`;
548
+ } else {
549
+ const m = VERSION_RE.exec(await res.text());
550
+ if (m) upstream = `${m[1]}.${m[2]}.${m[3]}`;
551
+ else reason = "parse-failed";
552
+ }
553
+ } catch {
554
+ upstream = null;
555
+ reason = "net-error";
556
+ }
557
+ const distinct = [...new Set(presented.values())].sort();
558
+ const presentedStr = distinct.length === 0 ? "none" : distinct.join(",");
559
+ if (upstream !== null && distinct.length > 0) {
560
+ if (distinct.some((v) => v !== upstream)) {
561
+ if (behindSince === null) behindSince = now().toISOString();
562
+ } else {
563
+ behindSince = null;
564
+ }
565
+ }
566
+ console.error(
567
+ `${TAG4} op=drift-census presented=${presentedStr} upstream=${upstream ?? reason} behindSince=${behindSince ?? "none"}`
568
+ );
569
+ return `presented=${presentedStr} upstream=${upstream ?? reason}`;
570
+ }
571
+ function startVersionDriftCensus(intervalMs = CENSUS_INTERVAL_MS) {
572
+ return registerLoop({
573
+ name: "whatsapp-version-drift",
574
+ intervalMs,
575
+ firstRunDelayMs: 0,
576
+ run: () => runVersionDriftCensus()
577
+ });
578
+ }
579
+
474
580
  // app/lib/whatsapp/session.ts
475
- var TAG4 = "[whatsapp:session]";
581
+ var TAG5 = "[whatsapp:session]";
476
582
  var WIRE_TAG = "[whatsapp:wire]";
477
583
  var baileysPkgVersion = (() => {
478
584
  try {
@@ -482,7 +588,7 @@ var baileysPkgVersion = (() => {
482
588
  return "unknown";
483
589
  }
484
590
  })();
485
- console.error(`${TAG4} baileys package loaded version=${baileysPkgVersion}`);
591
+ console.error(`${TAG5} baileys package loaded version=${baileysPkgVersion}`);
486
592
  var __BAILEYS_TAG = "[whatsapp:baileys]";
487
593
  var INSPECT_OPTS = {
488
594
  depth: null,
@@ -543,7 +649,7 @@ function createBaileysLogger(bindings = {}, authDir) {
543
649
  }
544
650
  var credsSaveQueue = Promise.resolve();
545
651
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
546
- console.error(`${TAG4} draining credential save queue\u2026`);
652
+ console.error(`${TAG5} draining credential save queue\u2026`);
547
653
  const timer = new Promise(
548
654
  (resolve6) => setTimeout(() => resolve6("timeout"), timeoutMs)
549
655
  );
@@ -552,9 +658,9 @@ async function drainCredsSaveQueue(timeoutMs = 5e3) {
552
658
  timer
553
659
  ]);
554
660
  if (result === "timeout") {
555
- console.error(`${TAG4} credential save queue drain timed out after ${timeoutMs}ms`);
661
+ console.error(`${TAG5} credential save queue drain timed out after ${timeoutMs}ms`);
556
662
  } else {
557
- console.error(`${TAG4} credential save queue drained`);
663
+ console.error(`${TAG5} credential save queue drained`);
558
664
  }
559
665
  }
560
666
  function readCredsJsonRaw2(filePath) {
@@ -577,11 +683,11 @@ async function safeSaveCreds(authDir, saveCreds) {
577
683
  JSON.parse(raw);
578
684
  fsSync2.copyFileSync(credsPath, backupPath);
579
685
  } catch (err) {
580
- console.warn(`${TAG4} backup pre-copy failed (parse or write) authDir=${authDir}: ${String(err)}`);
686
+ console.warn(`${TAG5} backup pre-copy failed (parse or write) authDir=${authDir}: ${String(err)}`);
581
687
  }
582
688
  }
583
689
  } catch (err) {
584
- console.warn(`${TAG4} backup preparation failed authDir=${authDir}: ${String(err)}`);
690
+ console.warn(`${TAG5} backup preparation failed authDir=${authDir}: ${String(err)}`);
585
691
  }
586
692
  try {
587
693
  await Promise.resolve(saveCreds());
@@ -589,39 +695,39 @@ async function safeSaveCreds(authDir, saveCreds) {
589
695
  try {
590
696
  const stats = await fs3.stat(credsPath);
591
697
  if (stats.size <= 1) {
592
- console.error(`${TAG4} WARNING: creds file empty/truncated after save path=${credsPath} size=${stats.size}`);
698
+ console.error(`${TAG5} WARNING: creds file empty/truncated after save path=${credsPath} size=${stats.size}`);
593
699
  } else {
594
- console.error(`${TAG4} creds saved and verified path=${credsPath} size=${stats.size}`);
700
+ console.error(`${TAG5} creds saved and verified path=${credsPath} size=${stats.size}`);
595
701
  }
596
702
  } catch (statErr) {
597
- console.error(`${TAG4} creds NOT found on disk after save path=${credsPath}: ${String(statErr)}`);
703
+ console.error(`${TAG5} creds NOT found on disk after save path=${credsPath}: ${String(statErr)}`);
598
704
  }
599
705
  } catch (err) {
600
- console.error(`${TAG4} failed saving creds to ${authDir}: ${String(err)}`);
706
+ console.error(`${TAG5} failed saving creds to ${authDir}: ${String(err)}`);
601
707
  }
602
708
  }
603
709
  function enqueueSaveCreds(authDir, saveCreds) {
604
710
  credsSaveQueue = credsSaveQueue.then(() => safeSaveCreds(authDir, saveCreds)).catch((err) => {
605
- console.error(`${TAG4} creds save queue error: ${String(err)}`);
711
+ console.error(`${TAG5} creds save queue error: ${String(err)}`);
606
712
  });
607
713
  }
608
714
  async function createWaSocket(opts) {
609
- const { authDir, onQr, onConnectionUpdate, silent, correlationId, account, getCodeIssuedTs } = opts;
715
+ const { authDir, onQr, onConnectionUpdate, silent, correlationId, account, getCodeIssuedTs, onCompanionReg } = opts;
610
716
  const cid = correlationId ?? "none";
611
717
  const acct = account ?? "unknown";
612
718
  await fs3.mkdir(authDir, { recursive: true });
613
719
  maybeRestoreCredsFromBackup(authDir);
614
720
  const { state, saveCreds } = await useMultiFileAuthState(authDir);
615
- const { version } = await fetchLatestBaileysVersion();
721
+ const { version } = await resolveWaVersion({ account: acct });
616
722
  if (!silent) {
617
723
  const preCensus = credsCensus(authDir);
618
724
  console.error(
619
- `${TAG4} op=creds-pre cid=${cid} account=${acct} credsPresent=${preCensus.credsPresent} registered=${preCensus.registered} hasAccount=${preCensus.hasAccountSignature}`
725
+ `${TAG5} op=creds-pre cid=${cid} account=${acct} credsPresent=${preCensus.credsPresent} registered=${preCensus.registered} hasAccount=${preCensus.hasAccountSignature}`
620
726
  );
621
727
  }
622
728
  const openTs = Date.now();
623
729
  console.error(
624
- `${TAG4} op=socket-open cid=${cid} account=${acct} baileysVersion=${baileysPkgVersion} waProtocolVersion=${version.join(".")} openTs=${openTs}`
730
+ `${TAG5} op=socket-open cid=${cid} account=${acct} baileysVersion=${baileysPkgVersion} waProtocolVersion=${version.join(".")} openTs=${openTs}`
625
731
  );
626
732
  const baileysLogger = createBaileysLogger({}, authDir);
627
733
  const sock = makeWASocket({
@@ -638,7 +744,7 @@ async function createWaSocket(opts) {
638
744
  fireInitQueries: false
639
745
  });
640
746
  sock.ev.on("creds.update", () => {
641
- console.error(`${TAG4} creds.update received \u2014 saving to ${authDir}`);
747
+ console.error(`${TAG5} creds.update received \u2014 saving to ${authDir}`);
642
748
  enqueueSaveCreds(authDir, saveCreds);
643
749
  });
644
750
  let qrSequence = 0;
@@ -658,14 +764,14 @@ async function createWaSocket(opts) {
658
764
  parts.push(`disconnectReason=${formatError(update.lastDisconnect.error)}`);
659
765
  }
660
766
  if (!silent && parts.length > 0) {
661
- console.error(`${TAG4} connection.update ${parts.join(" ")}`);
767
+ console.error(`${TAG5} connection.update ${parts.join(" ")}`);
662
768
  }
663
769
  if (update.qr && !silent) {
664
770
  const now = Date.now();
665
771
  const sinceLastRefMs = lastQrTs ? now - lastQrTs : null;
666
772
  lastQrTs = now;
667
773
  console.error(
668
- `${TAG4} op=qr cid=${cid} qr=#${qrSequence} sinceOpenMs=${now - openTs} sinceLastRefMs=${sinceLastRefMs ?? "n/a"}`
774
+ `${TAG5} op=qr cid=${cid} qr=#${qrSequence} sinceOpenMs=${now - openTs} sinceLastRefMs=${sinceLastRefMs ?? "n/a"}`
669
775
  );
670
776
  }
671
777
  if (update.isNewLogin === true && !pairSuccessLogged && !silent) {
@@ -674,7 +780,7 @@ async function createWaSocket(opts) {
674
780
  const sinceCodeIssuedMs = codeIssuedTs ? Date.now() - codeIssuedTs : null;
675
781
  const afterCensus = credsCensus(authDir);
676
782
  console.error(
677
- `${TAG4} op=pair-success cid=${cid} account=${acct} sinceCodeIssuedMs=${sinceCodeIssuedMs ?? "n/a"} hasAccountAfter=${afterCensus.hasAccountSignature}`
783
+ `${TAG5} op=pair-success cid=${cid} account=${acct} sinceCodeIssuedMs=${sinceCodeIssuedMs ?? "n/a"} hasAccountAfter=${afterCensus.hasAccountSignature}`
678
784
  );
679
785
  }
680
786
  if (update.qr && onQr) {
@@ -684,25 +790,25 @@ async function createWaSocket(opts) {
684
790
  const status = getStatusCode(update.lastDisconnect?.error);
685
791
  if (!silent) {
686
792
  console.error(
687
- `${TAG4} op=close cid=${cid} account=${acct} status=${status ?? "unknown"} reason="${formatError(update.lastDisconnect?.error)}" socketLifeMs=${Date.now() - openTs} kind=${closeKind(status, everOpen)}`
793
+ `${TAG5} op=close cid=${cid} account=${acct} status=${status ?? "unknown"} reason="${formatError(update.lastDisconnect?.error)}" socketLifeMs=${Date.now() - openTs} kind=${closeKind(status, everOpen)}`
688
794
  );
689
795
  }
690
796
  if (status === DisconnectReason.loggedOut && !silent) {
691
- console.error(`${TAG4} session logged out (401) \u2014 re-link required`);
797
+ console.error(`${TAG5} session logged out (401) \u2014 re-link required`);
692
798
  }
693
799
  }
694
800
  if (update.connection === "open") {
695
801
  everOpen = true;
696
- if (!silent) console.error(`${TAG4} connected`);
802
+ if (!silent) console.error(`${TAG5} connected`);
697
803
  }
698
804
  onConnectionUpdate?.(update);
699
805
  } catch (err) {
700
- console.error(`${TAG4} connection.update handler error: ${String(err)}`);
806
+ console.error(`${TAG5} connection.update handler error: ${String(err)}`);
701
807
  }
702
808
  });
703
809
  if (sock.ws && typeof sock.ws.on === "function") {
704
810
  sock.ws.on("error", (err) => {
705
- console.error(`${TAG4} WebSocket error: ${String(err)}`);
811
+ console.error(`${TAG5} WebSocket error: ${String(err)}`);
706
812
  });
707
813
  sock.ws.on("CB:message", (node) => {
708
814
  const attrs = node?.attrs ?? {};
@@ -718,6 +824,21 @@ async function createWaSocket(opts) {
718
824
  });
719
825
  }
720
826
  });
827
+ if (!silent) {
828
+ sock.ws.on("CB:notification,type:link_code_companion_reg", (node) => {
829
+ try {
830
+ const census = classifyCompanionReg(node);
831
+ console.error(
832
+ `${TAG5} op=companion-reg cid=${cid} account=${acct} stage=${census.stage} children=${census.children.join(",") || "none"} parseable=${census.parseable}`
833
+ );
834
+ onCompanionReg?.(census);
835
+ } catch (err) {
836
+ console.error(
837
+ `${TAG5} op=companion-reg-observer-error cid=${cid} account=${acct} err=${String(err)}`
838
+ );
839
+ }
840
+ });
841
+ }
721
842
  }
722
843
  return sock;
723
844
  }
@@ -826,6 +947,26 @@ function computeBackoff(attempt, config = {}) {
826
947
  return Math.round(jitter);
827
948
  }
828
949
 
950
+ // app/lib/whatsapp/link-census.ts
951
+ var HEALTH_TAG = "[whatsapp-health]";
952
+ var LINK_CENSUS_INTERVAL_MS = 15 * 6e4;
953
+ function downMsFor(row, now) {
954
+ if (row.connected) return "0";
955
+ if (row.lastConnectedAt === void 0) return "unknown";
956
+ return String(now - row.lastConnectedAt);
957
+ }
958
+ function linkCensusLines(rows, now) {
959
+ if (rows.length === 0) return [`${HEALTH_TAG} op=link-census accounts=0`];
960
+ return rows.map(
961
+ (row) => `${HEALTH_TAG} op=link-census account=${row.accountId} connected=${row.connected} stuck=${Boolean(row.sessionStuckReason)} downMs=${downMsFor(row, now)} lastError=${row.lastError ?? "none"}`
962
+ );
963
+ }
964
+ function runLinkCensus(read, now) {
965
+ const rows = read();
966
+ for (const line of linkCensusLines(rows, now)) console.error(line);
967
+ return rows.length;
968
+ }
969
+
829
970
  // app/lib/whatsapp/init-queries.ts
830
971
  import { inspect as inspect2 } from "util";
831
972
 
@@ -855,7 +996,7 @@ function withTimeout(label, promise, timeoutMs) {
855
996
  }
856
997
 
857
998
  // app/lib/whatsapp/init-queries.ts
858
- var TAG5 = "[whatsapp:init]";
999
+ var TAG6 = "[whatsapp:init]";
859
1000
  var DEFAULT_QUERY_TIMEOUT_MS = 15e3;
860
1001
  var INSPECT_OPTS2 = {
861
1002
  depth: null,
@@ -883,20 +1024,20 @@ async function runInitQueries(sock, ctx) {
883
1024
  elapsedMs: 0
884
1025
  };
885
1026
  console.error(
886
- `${TAG5} runInitQueries start account=${ctx.accountId} attempt=${ctx.attempt} selfPhone=${ctx.selfPhone ?? "unknown"} authDir=${ctx.authDir} queryTimeoutMs=${queryTimeoutMs}`
1027
+ `${TAG6} runInitQueries start account=${ctx.accountId} attempt=${ctx.attempt} selfPhone=${ctx.selfPhone ?? "unknown"} authDir=${ctx.authDir} queryTimeoutMs=${queryTimeoutMs}`
887
1028
  );
888
1029
  console.error(
889
- `${TAG5} fetchProps SKIPPED account=${ctx.accountId} reason=no-platform-consumer`
1030
+ `${TAG6} fetchProps SKIPPED account=${ctx.accountId} reason=no-platform-consumer`
890
1031
  );
891
1032
  if (ctx.signal?.aborted) {
892
1033
  result.aborted = true;
893
1034
  result.elapsedMs = Date.now() - startedAt;
894
1035
  console.error(
895
- `${TAG5} runInitQueries aborted before start account=${ctx.accountId}`
1036
+ `${TAG6} runInitQueries aborted before start account=${ctx.accountId}`
896
1037
  );
897
1038
  return result;
898
1039
  }
899
- console.error(`${TAG5} fetchBlocklist start account=${ctx.accountId}`);
1040
+ console.error(`${TAG6} fetchBlocklist start account=${ctx.accountId}`);
900
1041
  try {
901
1042
  const blocklist = await withTimeout(
902
1043
  "fetchBlocklist",
@@ -906,24 +1047,24 @@ async function runInitQueries(sock, ctx) {
906
1047
  const count = Array.isArray(blocklist) ? blocklist.length : 0;
907
1048
  result.blocklistOk = true;
908
1049
  console.error(
909
- `${TAG5} fetchBlocklist ok account=${ctx.accountId} count=${count}`
1050
+ `${TAG6} fetchBlocklist ok account=${ctx.accountId} count=${count}`
910
1051
  );
911
1052
  } catch (err) {
912
1053
  const formatted = formatErr(err);
913
1054
  result.errors.blocklist = formatted;
914
1055
  console.error(
915
- `${TAG5} fetchBlocklist FAILED account=${ctx.accountId}: ${formatted}`
1056
+ `${TAG6} fetchBlocklist FAILED account=${ctx.accountId}: ${formatted}`
916
1057
  );
917
1058
  }
918
1059
  if (ctx.signal?.aborted) {
919
1060
  result.aborted = true;
920
1061
  result.elapsedMs = Date.now() - startedAt;
921
1062
  console.error(
922
- `${TAG5} runInitQueries aborted mid-sequence account=${ctx.accountId} after=fetchBlocklist`
1063
+ `${TAG6} runInitQueries aborted mid-sequence account=${ctx.accountId} after=fetchBlocklist`
923
1064
  );
924
1065
  return result;
925
1066
  }
926
- console.error(`${TAG5} fetchPrivacySettings start account=${ctx.accountId}`);
1067
+ console.error(`${TAG6} fetchPrivacySettings start account=${ctx.accountId}`);
927
1068
  try {
928
1069
  const settings = await withTimeout(
929
1070
  "fetchPrivacySettings",
@@ -933,18 +1074,18 @@ async function runInitQueries(sock, ctx) {
933
1074
  );
934
1075
  result.privacySettingsOk = true;
935
1076
  console.error(
936
- `${TAG5} fetchPrivacySettings ok account=${ctx.accountId} keys=${settings ? Object.keys(settings).join(",") : ""}`
1077
+ `${TAG6} fetchPrivacySettings ok account=${ctx.accountId} keys=${settings ? Object.keys(settings).join(",") : ""}`
937
1078
  );
938
1079
  } catch (err) {
939
1080
  const formatted = formatErr(err);
940
1081
  result.errors.privacySettings = formatted;
941
1082
  console.error(
942
- `${TAG5} fetchPrivacySettings FAILED account=${ctx.accountId}: ${formatted}`
1083
+ `${TAG6} fetchPrivacySettings FAILED account=${ctx.accountId}: ${formatted}`
943
1084
  );
944
1085
  }
945
1086
  result.elapsedMs = Date.now() - startedAt;
946
1087
  console.error(
947
- `${TAG5} runInitQueries done account=${ctx.accountId} blocklistOk=${result.blocklistOk} privacySettingsOk=${result.privacySettingsOk} elapsedMs=${result.elapsedMs}`
1088
+ `${TAG6} runInitQueries done account=${ctx.accountId} blocklistOk=${result.blocklistOk} privacySettingsOk=${result.privacySettingsOk} elapsedMs=${result.elapsedMs}`
948
1089
  );
949
1090
  return result;
950
1091
  }
@@ -1218,16 +1359,16 @@ function toMillis(v) {
1218
1359
  if (typeof v.low === "number") return (v.low >>> 0) + (v.high ?? 0) * 4294967296;
1219
1360
  return null;
1220
1361
  }
1221
- function readReactionEntry(entry) {
1222
- const targetMsgKeyId = entry.key?.id ?? void 0;
1362
+ function readReactionEntry(entry2) {
1363
+ const targetMsgKeyId = entry2.key?.id ?? void 0;
1223
1364
  if (!targetMsgKeyId) return null;
1224
- const chatJid = entry.key?.remoteJid ?? entry.reaction?.key?.remoteJid ?? void 0;
1365
+ const chatJid = entry2.key?.remoteJid ?? entry2.reaction?.key?.remoteJid ?? void 0;
1225
1366
  if (!chatJid) return null;
1226
1367
  const isGroup = isGroupJid(chatJid);
1227
- const reactorKey = entry.reaction?.key;
1368
+ const reactorKey = entry2.reaction?.key;
1228
1369
  const reactorJid = isGroup ? reactorKey?.participant ?? void 0 : reactorKey?.remoteJid ?? chatJid;
1229
1370
  if (!reactorJid) return null;
1230
- const emoji = entry.reaction?.text ?? "";
1371
+ const emoji = entry2.reaction?.text ?? "";
1231
1372
  return {
1232
1373
  targetMsgKeyId,
1233
1374
  chatJid,
@@ -1236,7 +1377,7 @@ function readReactionEntry(entry) {
1236
1377
  emoji,
1237
1378
  removed: !emoji,
1238
1379
  fromMe: !!reactorKey?.fromMe,
1239
- timestampMs: toMillis(entry.reaction?.senderTimestampMs)
1380
+ timestampMs: toMillis(entry2.reaction?.senderTimestampMs)
1240
1381
  };
1241
1382
  }
1242
1383
 
@@ -1381,7 +1522,7 @@ function summariseRecords(records) {
1381
1522
  return {
1382
1523
  first: sorted[0],
1383
1524
  last: sorted[sorted.length - 1],
1384
- firstNamedSender: sorted.find((r) => r.senderName)?.senderName ?? null
1525
+ firstNamedSender: sorted.find((r) => !r.fromMe && r.senderName)?.senderName ?? null
1385
1526
  };
1386
1527
  }
1387
1528
  function readConversationSummaries(platformAccountId) {
@@ -1393,10 +1534,10 @@ function readConversationSummaries(platformAccountId) {
1393
1534
  let bytesRead = 0;
1394
1535
  try {
1395
1536
  if (!existsSync3(dir)) return out;
1396
- for (const entry of readdirSync2(dir)) {
1397
- if (!entry.endsWith(".jsonl")) continue;
1398
- const remoteJid = entry.slice(0, -".jsonl".length);
1399
- const file = join2(dir, entry);
1537
+ for (const entry2 of readdirSync2(dir)) {
1538
+ if (!entry2.endsWith(".jsonl")) continue;
1539
+ const remoteJid = entry2.slice(0, -".jsonl".length);
1540
+ const file = join2(dir, entry2);
1400
1541
  seen.add(file);
1401
1542
  let size;
1402
1543
  let mtimeMs;
@@ -1445,9 +1586,9 @@ function readAllConversations(platformAccountId) {
1445
1586
  const out = /* @__PURE__ */ new Map();
1446
1587
  try {
1447
1588
  if (!existsSync3(dir)) return out;
1448
- for (const entry of readdirSync2(dir)) {
1449
- if (!entry.endsWith(".jsonl")) continue;
1450
- const remoteJid = entry.slice(0, -".jsonl".length);
1589
+ for (const entry2 of readdirSync2(dir)) {
1590
+ if (!entry2.endsWith(".jsonl")) continue;
1591
+ const remoteJid = entry2.slice(0, -".jsonl".length);
1451
1592
  out.set(remoteJid, readConversation(platformAccountId, remoteJid));
1452
1593
  }
1453
1594
  } catch (err) {
@@ -1468,16 +1609,16 @@ function normalizeEntry(v) {
1468
1609
  return typeof v === "string" ? { account: v, mode: "active" } : { account: v.managesAccount, mode: v.mode ?? "active" };
1469
1610
  }
1470
1611
  function managedAccountFor(accountManagers, phone) {
1471
- for (const [managerPhone, entry] of Object.entries(accountManagers)) {
1472
- const { account, mode } = normalizeEntry(entry);
1612
+ for (const [managerPhone, entry2] of Object.entries(accountManagers)) {
1613
+ const { account, mode } = normalizeEntry(entry2);
1473
1614
  if (account && phonesMatch(managerPhone, phone)) return { account, mode };
1474
1615
  }
1475
1616
  return null;
1476
1617
  }
1477
1618
  function managerPhonesForAccount(accountManagers, subAccountId) {
1478
1619
  const phones = [];
1479
- for (const [managerPhone, entry] of Object.entries(accountManagers)) {
1480
- const { account } = normalizeEntry(entry);
1620
+ for (const [managerPhone, entry2] of Object.entries(accountManagers)) {
1621
+ const { account } = normalizeEntry(entry2);
1481
1622
  if (account && account === subAccountId) phones.push(managerPhone);
1482
1623
  }
1483
1624
  return phones;
@@ -1498,7 +1639,7 @@ function resolveAccountConfig(config, accountConfig) {
1498
1639
  function isAdminPhone(phone, adminPhones) {
1499
1640
  const normalized = normalizeE164(phone);
1500
1641
  if (!normalized) return false;
1501
- return adminPhones.some((entry) => phonesMatch(entry, normalized));
1642
+ return adminPhones.some((entry2) => phonesMatch(entry2, normalized));
1502
1643
  }
1503
1644
  function checkDmAccess(params) {
1504
1645
  const { senderPhone, selfPhone, accountId } = params;
@@ -1520,9 +1661,9 @@ function checkDmAccess(params) {
1520
1661
  case "open":
1521
1662
  return { allowed: true, reason: "dm-policy-open", agentType: "public", effectiveAccountId: accountId };
1522
1663
  case "allowlist": {
1523
- const inList = cfg.allowFrom.some((entry) => {
1524
- if (entry === "*") return true;
1525
- return phonesMatch(entry, senderPhone);
1664
+ const inList = cfg.allowFrom.some((entry2) => {
1665
+ if (entry2 === "*") return true;
1666
+ return phonesMatch(entry2, senderPhone);
1526
1667
  });
1527
1668
  if (inList) {
1528
1669
  return { allowed: true, reason: "allowlist-match", agentType: "public", effectiveAccountId: accountId };
@@ -1597,8 +1738,8 @@ var NOTIFY_MAX = 5e3;
1597
1738
  var notifyCache = /* @__PURE__ */ new Map();
1598
1739
  function isNotifyDuplicate(msgKeyId) {
1599
1740
  const now = Date.now();
1600
- const entry = notifyCache.get(msgKeyId);
1601
- if (entry && now - entry.ts <= NOTIFY_TTL_MS) return true;
1741
+ const entry2 = notifyCache.get(msgKeyId);
1742
+ if (entry2 && now - entry2.ts <= NOTIFY_TTL_MS) return true;
1602
1743
  for (const [key, e] of notifyCache) {
1603
1744
  if (now - e.ts > NOTIFY_TTL_MS) notifyCache.delete(key);
1604
1745
  }
@@ -1675,15 +1816,15 @@ var ACCESS_LOCKOUT_MS = 15 * 60 * 1e3;
1675
1816
  var ACCESS_WINDOW_MS = 15 * 60 * 1e3;
1676
1817
  function checkAccessRateLimit(ip, agentSlug) {
1677
1818
  const key = `${ip}:${agentSlug}`;
1678
- const entry = rateLimitMap.get(key);
1679
- if (!entry) return null;
1819
+ const entry2 = rateLimitMap.get(key);
1820
+ if (!entry2) return null;
1680
1821
  const now = Date.now();
1681
- if (entry.lockedUntil && now < entry.lockedUntil) {
1682
- const remainingMs = entry.lockedUntil - now;
1822
+ if (entry2.lockedUntil && now < entry2.lockedUntil) {
1823
+ const remainingMs = entry2.lockedUntil - now;
1683
1824
  const remainingS = Math.ceil(remainingMs / 1e3);
1684
1825
  return `Too many attempts. Try again in ${remainingS}s`;
1685
1826
  }
1686
- if (now - entry.firstAttempt > ACCESS_WINDOW_MS) {
1827
+ if (now - entry2.firstAttempt > ACCESS_WINDOW_MS) {
1687
1828
  rateLimitMap.delete(key);
1688
1829
  return null;
1689
1830
  }
@@ -1692,14 +1833,14 @@ function checkAccessRateLimit(ip, agentSlug) {
1692
1833
  function recordAccessFailedAttempt(ip, agentSlug) {
1693
1834
  const key = `${ip}:${agentSlug}`;
1694
1835
  const now = Date.now();
1695
- const entry = rateLimitMap.get(key);
1696
- if (!entry || now - entry.firstAttempt > ACCESS_WINDOW_MS) {
1836
+ const entry2 = rateLimitMap.get(key);
1837
+ if (!entry2 || now - entry2.firstAttempt > ACCESS_WINDOW_MS) {
1697
1838
  rateLimitMap.set(key, { attempts: 1, firstAttempt: now });
1698
1839
  return;
1699
1840
  }
1700
- entry.attempts++;
1701
- if (entry.attempts >= ACCESS_MAX_ATTEMPTS) {
1702
- entry.lockedUntil = now + ACCESS_LOCKOUT_MS;
1841
+ entry2.attempts++;
1842
+ if (entry2.attempts >= ACCESS_MAX_ATTEMPTS) {
1843
+ entry2.lockedUntil = now + ACCESS_LOCKOUT_MS;
1703
1844
  }
1704
1845
  }
1705
1846
  function clearAccessRateLimit(ip, agentSlug) {
@@ -1709,15 +1850,15 @@ var contactRateMap = /* @__PURE__ */ new Map();
1709
1850
  var REQUEST_LINK_MAX = 3;
1710
1851
  var REQUEST_LINK_WINDOW_MS = 60 * 60 * 1e3;
1711
1852
  function checkRequestLinkRateLimit(contactValue) {
1712
- const entry = contactRateMap.get(contactValue);
1713
- if (!entry) return null;
1853
+ const entry2 = contactRateMap.get(contactValue);
1854
+ if (!entry2) return null;
1714
1855
  const now = Date.now();
1715
- if (now - entry.windowStart > REQUEST_LINK_WINDOW_MS) {
1856
+ if (now - entry2.windowStart > REQUEST_LINK_WINDOW_MS) {
1716
1857
  contactRateMap.delete(contactValue);
1717
1858
  return null;
1718
1859
  }
1719
- if (entry.count >= REQUEST_LINK_MAX) {
1720
- const remainingMs = REQUEST_LINK_WINDOW_MS - (now - entry.windowStart);
1860
+ if (entry2.count >= REQUEST_LINK_MAX) {
1861
+ const remainingMs = REQUEST_LINK_WINDOW_MS - (now - entry2.windowStart);
1721
1862
  const remainingM = Math.ceil(remainingMs / 6e4);
1722
1863
  return `Too many requests. Try again in ${remainingM} minute(s)`;
1723
1864
  }
@@ -1725,12 +1866,12 @@ function checkRequestLinkRateLimit(contactValue) {
1725
1866
  }
1726
1867
  function recordRequestLinkAttempt(contactValue) {
1727
1868
  const now = Date.now();
1728
- const entry = contactRateMap.get(contactValue);
1729
- if (!entry || now - entry.windowStart > REQUEST_LINK_WINDOW_MS) {
1869
+ const entry2 = contactRateMap.get(contactValue);
1870
+ if (!entry2 || now - entry2.windowStart > REQUEST_LINK_WINDOW_MS) {
1730
1871
  contactRateMap.set(contactValue, { count: 1, windowStart: now });
1731
1872
  return;
1732
1873
  }
1733
- entry.count++;
1874
+ entry2.count++;
1734
1875
  }
1735
1876
  function maskContact(value) {
1736
1877
  if (value.includes("@")) {
@@ -1919,8 +2060,8 @@ var agentSentCache = /* @__PURE__ */ new Map();
1919
2060
  var inboundCache = /* @__PURE__ */ new Map();
1920
2061
  function pruneCache(cache3, ttl, max) {
1921
2062
  const now = Date.now();
1922
- for (const [key, entry] of cache3) {
1923
- if (now - entry.ts > ttl) cache3.delete(key);
2063
+ for (const [key, entry2] of cache3) {
2064
+ if (now - entry2.ts > ttl) cache3.delete(key);
1924
2065
  }
1925
2066
  if (cache3.size > max) {
1926
2067
  const excess = cache3.size - max;
@@ -1937,22 +2078,22 @@ function trackAgentSentMessage(messageId) {
1937
2078
  agentSentCache.set(messageId, { ts: Date.now() });
1938
2079
  }
1939
2080
  function isAgentSentMessage(messageId) {
1940
- const entry = agentSentCache.get(messageId);
1941
- if (!entry) return false;
1942
- if (Date.now() - entry.ts > AGENT_SENT_TTL_MS) {
2081
+ const entry2 = agentSentCache.get(messageId);
2082
+ if (!entry2) return false;
2083
+ if (Date.now() - entry2.ts > AGENT_SENT_TTL_MS) {
1943
2084
  agentSentCache.delete(messageId);
1944
2085
  return false;
1945
2086
  }
1946
2087
  return true;
1947
2088
  }
1948
2089
  function isDuplicateInbound(key) {
1949
- const entry = inboundCache.get(key);
1950
- if (!entry) {
2090
+ const entry2 = inboundCache.get(key);
2091
+ if (!entry2) {
1951
2092
  pruneCache(inboundCache, INBOUND_TTL_MS, INBOUND_MAX);
1952
2093
  inboundCache.set(key, { ts: Date.now() });
1953
2094
  return false;
1954
2095
  }
1955
- if (Date.now() - entry.ts > INBOUND_TTL_MS) {
2096
+ if (Date.now() - entry2.ts > INBOUND_TTL_MS) {
1956
2097
  inboundCache.delete(key);
1957
2098
  inboundCache.set(key, { ts: Date.now() });
1958
2099
  return false;
@@ -1961,7 +2102,7 @@ function isDuplicateInbound(key) {
1961
2102
  }
1962
2103
 
1963
2104
  // app/lib/whatsapp/inbound/self-chat.ts
1964
- var TAG6 = "[whatsapp:self-chat]";
2105
+ var TAG7 = "[whatsapp:self-chat]";
1965
2106
  function normalizeJid(jid) {
1966
2107
  const m = jid.match(/^(\d+)(?::\d+)?@(s\.whatsapp\.net|lid)$/i);
1967
2108
  return m ? `${m[1]}@${m[2].toLowerCase()}` : jid;
@@ -1981,10 +2122,10 @@ function evaluateSelfChatCommand(input) {
1981
2122
  const connAtMs = input.lastConnectedAtMs ?? 0;
1982
2123
  const msgId = input.msgId ?? "?";
1983
2124
  console.error(
1984
- `${TAG6} op=seen account=${input.accountId} msgId=${msgId} tsMs=${tsMs} connAtMs=${connAtMs} fromMe=true`
2125
+ `${TAG7} op=seen account=${input.accountId} msgId=${msgId} tsMs=${tsMs} connAtMs=${connAtMs} fromMe=true`
1985
2126
  );
1986
2127
  const skip = (reason) => {
1987
- console.error(`${TAG6} op=skip account=${input.accountId} msgId=${msgId} reason=${reason}`);
2128
+ console.error(`${TAG7} op=skip account=${input.accountId} msgId=${msgId} reason=${reason}`);
1988
2129
  return { kind: "skip", reason };
1989
2130
  };
1990
2131
  if (input.msgId && isAgentSentMessage(input.msgId)) return skip("echo");
@@ -1992,13 +2133,13 @@ function evaluateSelfChatCommand(input) {
1992
2133
  if (isDuplicateInbound(`${input.accountId}:${input.remoteJid}:${input.msgId}`)) return skip("duplicate");
1993
2134
  if (!input.text) return skip("no-text");
1994
2135
  console.error(
1995
- `${TAG6} op=dispatch account=${input.accountId} msgId=${msgId} bytes=${Buffer.byteLength(input.text, "utf8")} agentType=admin`
2136
+ `${TAG7} op=dispatch account=${input.accountId} msgId=${msgId} bytes=${Buffer.byteLength(input.text, "utf8")} agentType=admin`
1996
2137
  );
1997
2138
  return { kind: "dispatch", text: input.text };
1998
2139
  }
1999
2140
 
2000
2141
  // app/lib/whatsapp/activity.ts
2001
- var TAG7 = "[whatsapp:activity]";
2142
+ var TAG8 = "[whatsapp:activity]";
2002
2143
  var RECENT_EVENTS_MAX = 200;
2003
2144
  var counters = /* @__PURE__ */ new Map();
2004
2145
  var recentEvents = [];
@@ -2013,7 +2154,7 @@ function recordActivity(event) {
2013
2154
  }
2014
2155
  acct[direction]++;
2015
2156
  acct.lastEventAt = now;
2016
- const entry = {
2157
+ const entry2 = {
2017
2158
  channel: "whatsapp",
2018
2159
  accountId,
2019
2160
  direction,
@@ -2021,15 +2162,15 @@ function recordActivity(event) {
2021
2162
  timestamp: now,
2022
2163
  messageType
2023
2164
  };
2024
- recentEvents.push(entry);
2165
+ recentEvents.push(entry2);
2025
2166
  if (recentEvents.length > RECENT_EVENTS_MAX) {
2026
2167
  recentEvents.splice(0, recentEvents.length - RECENT_EVENTS_MAX);
2027
2168
  }
2028
2169
  console.error(
2029
- `${TAG7} channel-activity: direction=${direction} account=${accountId} jid=${jid}` + (messageType ? ` type=${messageType}` : "")
2170
+ `${TAG8} channel-activity: direction=${direction} account=${accountId} jid=${jid}` + (messageType ? ` type=${messageType}` : "")
2030
2171
  );
2031
2172
  } catch (err) {
2032
- console.error(`${TAG7} recording failed: ${String(err)}`);
2173
+ console.error(`${TAG8} recording failed: ${String(err)}`);
2033
2174
  }
2034
2175
  }
2035
2176
  function getChannelActivity(accountId) {
@@ -2050,7 +2191,7 @@ function getChannelActivity(accountId) {
2050
2191
  }
2051
2192
 
2052
2193
  // app/lib/whatsapp/outbound/send.ts
2053
- var TAG8 = "[whatsapp:outbound]";
2194
+ var TAG9 = "[whatsapp:outbound]";
2054
2195
  function buildQuoted(q) {
2055
2196
  return {
2056
2197
  key: {
@@ -2071,14 +2212,14 @@ async function sendTextMessage(sock, to, text, opts) {
2071
2212
  const messageId = result?.key?.id;
2072
2213
  if (messageId) {
2073
2214
  trackAgentSentMessage(messageId);
2074
- console.error(`${TAG8} sent text to=${jid} id=${messageId}`);
2215
+ console.error(`${TAG9} sent text to=${jid} id=${messageId}`);
2075
2216
  }
2076
2217
  if (opts?.accountId) {
2077
2218
  recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: "text" });
2078
2219
  }
2079
2220
  return { success: true, messageId: messageId ?? void 0 };
2080
2221
  } catch (err) {
2081
- console.error(`${TAG8} send failed to=${to}: ${String(err)}`);
2222
+ console.error(`${TAG9} send failed to=${to}: ${String(err)}`);
2082
2223
  return { success: false, error: String(err) };
2083
2224
  }
2084
2225
  }
@@ -2155,20 +2296,20 @@ async function sendMediaMessage(sock, to, media, opts) {
2155
2296
  const messageId = result?.key?.id;
2156
2297
  if (messageId) {
2157
2298
  trackAgentSentMessage(messageId);
2158
- console.error(`${TAG8} sent ${media.type} to=${jid} id=${messageId}`);
2299
+ console.error(`${TAG9} sent ${media.type} to=${jid} id=${messageId}`);
2159
2300
  }
2160
2301
  if (opts?.accountId) {
2161
2302
  recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: media.type });
2162
2303
  }
2163
2304
  return { success: true, messageId: messageId ?? void 0 };
2164
2305
  } catch (err) {
2165
- console.error(`${TAG8} send media failed to=${to}: ${String(err)}`);
2306
+ console.error(`${TAG9} send media failed to=${to}: ${String(err)}`);
2166
2307
  return { success: false, error: String(err) };
2167
2308
  }
2168
2309
  }
2169
2310
 
2170
2311
  // app/lib/whatsapp/persist-message.ts
2171
- var TAG9 = "[whatsapp-persist]";
2312
+ var TAG10 = "[whatsapp-persist]";
2172
2313
  var sessionWriteLocks = /* @__PURE__ */ new Map();
2173
2314
  async function persistWhatsAppMessage(input) {
2174
2315
  if (!input.body && !input.attachmentId || !input.msgKeyId) return null;
@@ -2219,14 +2360,14 @@ async function persistWhatsAppMessage(input) {
2219
2360
  };
2220
2361
  const result = await session.run(cypher, params);
2221
2362
  if (result.records.length === 0) {
2222
- console.error(`${TAG9} skip reason=conversation-not-found accountId=${input.platformAccountId} cacheKey=${input.cacheKey} messageId=${messageId}`);
2363
+ console.error(`${TAG10} skip reason=conversation-not-found accountId=${input.platformAccountId} cacheKey=${input.cacheKey} messageId=${messageId}`);
2223
2364
  return null;
2224
2365
  }
2225
2366
  const rec = result.records[0];
2226
2367
  const senderElementId = rec.get("senderElementId");
2227
2368
  const senderReused = rec.get("senderReused") === true;
2228
2369
  console.error(
2229
- `${TAG9} sender-resolved telephone=${senderTelephone} nodeKind=Person elementId=${senderElementId} reused=${senderReused}`
2370
+ `${TAG10} sender-resolved telephone=${senderTelephone} nodeKind=Person elementId=${senderElementId} reused=${senderReused}`
2230
2371
  );
2231
2372
  const diskRecord = {
2232
2373
  messageId,
@@ -2252,18 +2393,18 @@ async function persistWhatsAppMessage(input) {
2252
2393
  const { created } = appendMessage(input.platformAccountId, diskRecord, altDedupeRemoteJids);
2253
2394
  const ms = Date.now() - t0;
2254
2395
  if (!created) {
2255
- console.error(`${TAG9} noop reason=existing-message messageId=${messageId}`);
2396
+ console.error(`${TAG10} noop reason=existing-message messageId=${messageId}`);
2256
2397
  return { messageId, created: false, senderElementId };
2257
2398
  }
2258
2399
  console.error(
2259
- `${TAG9} write messageId=${messageId} accountId=${input.platformAccountId} cacheKey=${input.cacheKey} fromMe=${input.fromMe} dateSent=${dateSentIso} bodyBytes=${Buffer.byteLength(input.body, "utf8")} attachment=${input.attachmentId ?? "none"} ms=${ms}`
2400
+ `${TAG10} write messageId=${messageId} accountId=${input.platformAccountId} cacheKey=${input.cacheKey} fromMe=${input.fromMe} dateSent=${dateSentIso} bodyBytes=${Buffer.byteLength(input.body, "utf8")} attachment=${input.attachmentId ?? "none"} ms=${ms}`
2260
2401
  );
2261
2402
  return { messageId, created: true, senderElementId };
2262
2403
  } catch (err) {
2263
2404
  const ms = Date.now() - t0;
2264
2405
  const reason = sanitizeReason(err);
2265
2406
  console.error(
2266
- `${TAG9} FAIL accountId=${input.platformAccountId} waname=${input.accountId} remoteJid=${input.remoteJid} msgKey=${input.msgKeyId} reason=${reason} ms=${ms}`
2407
+ `${TAG10} FAIL accountId=${input.platformAccountId} waname=${input.accountId} remoteJid=${input.remoteJid} msgKey=${input.msgKeyId} reason=${reason} ms=${ms}`
2267
2408
  );
2268
2409
  return null;
2269
2410
  } finally {
@@ -2295,8 +2436,218 @@ function sanitizeReason(err) {
2295
2436
  return `${err.name}:${msg}`;
2296
2437
  }
2297
2438
 
2439
+ // app/lib/whatsapp/chat-metadata-store.ts
2440
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync, statSync as statSync2 } from "fs";
2441
+ import { dirname as dirname2, join as join4 } from "path";
2442
+
2443
+ // app/lib/data-path.ts
2444
+ import { realpathSync } from "fs";
2445
+ import { resolve as resolve2, normalize, sep, relative, basename, join as join3 } from "path";
2446
+ var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "../platform");
2447
+ var DATA_ROOT = resolve2(PLATFORM_ROOT2, "..", "data");
2448
+ var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve2(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
2449
+ var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["accounts"]);
2450
+ var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2451
+ function crossesForeignAccountPartition(relPath, accountId) {
2452
+ const segs = relPath.split("/").filter(Boolean);
2453
+ if (segs.length < 2) return false;
2454
+ if (!ACCOUNT_PARTITION_DIRS.has(segs[0])) return false;
2455
+ const owner = segs[1];
2456
+ if (!ACCOUNT_UUID_RE.test(owner)) return false;
2457
+ return owner !== accountId;
2458
+ }
2459
+ function isWithinOwnAccountPartition(relPath, accountId) {
2460
+ const segs = relPath.split("/").filter(Boolean);
2461
+ return segs.length >= 2 && segs[0] === "accounts" && segs[1] === accountId;
2462
+ }
2463
+ function resolveUnderRoot(raw, rootAbs, rootLabel) {
2464
+ const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
2465
+ const absolute = resolve2(rootAbs, cleaned);
2466
+ let rootReal;
2467
+ try {
2468
+ rootReal = realpathSync(rootAbs);
2469
+ } catch (err) {
2470
+ return {
2471
+ ok: false,
2472
+ status: 500,
2473
+ error: `${rootLabel} not accessible: ${err instanceof Error ? err.message : String(err)}`
2474
+ };
2475
+ }
2476
+ let resolvedReal;
2477
+ try {
2478
+ resolvedReal = realpathSync(absolute);
2479
+ } catch (err) {
2480
+ const code = err.code;
2481
+ if (code === "ENOENT") {
2482
+ if (absolute !== rootReal && !absolute.startsWith(rootReal + sep)) {
2483
+ return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: absolute };
2484
+ }
2485
+ return { ok: false, status: 404, error: "Not found" };
2486
+ }
2487
+ return {
2488
+ ok: false,
2489
+ status: 500,
2490
+ error: err instanceof Error ? err.message : String(err)
2491
+ };
2492
+ }
2493
+ if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + sep)) {
2494
+ return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: resolvedReal };
2495
+ }
2496
+ const relPath = relative(rootReal, resolvedReal) || ".";
2497
+ return { ok: true, absolute: resolvedReal, dataRootReal: rootReal, relative: relPath };
2498
+ }
2499
+ function resolveDataPath(raw) {
2500
+ return resolveUnderRoot(raw, DATA_ROOT, "DATA_ROOT");
2501
+ }
2502
+ function resolveClaudeUploadsPath(raw) {
2503
+ if (!CLAUDE_UPLOADS_ROOT) {
2504
+ return { ok: false, status: 404, error: "Not found" };
2505
+ }
2506
+ return resolveUnderRoot(raw, CLAUDE_UPLOADS_ROOT, "CLAUDE_UPLOADS_ROOT");
2507
+ }
2508
+ function scratchpadTmpBase() {
2509
+ return process.env.MAXY_SCRATCHPAD_BASE ?? "/tmp";
2510
+ }
2511
+ function sessionScratchpadRoot(projectDir, sessionId) {
2512
+ const uid = typeof process.geteuid === "function" ? process.geteuid() : 0;
2513
+ return join3(scratchpadTmpBase(), `claude-${uid}`, basename(projectDir), sessionId, "scratchpad");
2514
+ }
2515
+ function resolveScratchpadPath(raw, scratchpadRootAbs) {
2516
+ return resolveUnderRoot(raw, scratchpadRootAbs, "scratchpad");
2517
+ }
2518
+
2519
+ // app/lib/whatsapp/chat-metadata-store.ts
2520
+ var SOURCE_RANK = { contact: 3, group: 2, chat: 1 };
2521
+ function isSource(v) {
2522
+ return v === "contact" || v === "group" || v === "chat";
2523
+ }
2524
+ function preferEntry(existing, incoming) {
2525
+ if (existing === void 0) return incoming;
2526
+ const a = SOURCE_RANK[existing.source];
2527
+ const b = SOURCE_RANK[incoming.source];
2528
+ if (b > a) return incoming;
2529
+ if (b < a) return existing;
2530
+ return incoming.updatedAt > existing.updatedAt ? incoming : existing;
2531
+ }
2532
+ function parseChatMetadata(raw) {
2533
+ const out = /* @__PURE__ */ new Map();
2534
+ let parsed;
2535
+ try {
2536
+ parsed = JSON.parse(raw);
2537
+ } catch {
2538
+ return out;
2539
+ }
2540
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return out;
2541
+ for (const [jid, value] of Object.entries(parsed)) {
2542
+ if (value === null || typeof value !== "object") continue;
2543
+ const v = value;
2544
+ if (typeof v.name !== "string" || v.name.length === 0) continue;
2545
+ if (!isSource(v.source)) continue;
2546
+ if (typeof v.updatedAt !== "string") continue;
2547
+ out.set(jid, {
2548
+ jid,
2549
+ name: v.name,
2550
+ source: v.source,
2551
+ isGroup: v.isGroup === true,
2552
+ updatedAt: v.updatedAt
2553
+ });
2554
+ }
2555
+ return out;
2556
+ }
2557
+ function serializeChatMetadata(map) {
2558
+ const obj = {};
2559
+ for (const [jid, entry2] of map) obj[jid] = entry2;
2560
+ return JSON.stringify(obj);
2561
+ }
2562
+ function chatMetadataPath(accountId) {
2563
+ return join4(DATA_ROOT, "accounts", accountId, "chat-metadata.json");
2564
+ }
2565
+ var readCache = /* @__PURE__ */ new Map();
2566
+ function readChatMetadata(accountId) {
2567
+ const path3 = chatMetadataPath(accountId);
2568
+ try {
2569
+ if (!existsSync4(path3)) return /* @__PURE__ */ new Map();
2570
+ const st = statSync2(path3);
2571
+ const cached = readCache.get(path3);
2572
+ if (cached && cached.size === st.size && cached.mtimeMs === st.mtimeMs) return cached.map;
2573
+ const map = parseChatMetadata(readFileSync5(path3, "utf8"));
2574
+ readCache.set(path3, { size: st.size, mtimeMs: st.mtimeMs, map });
2575
+ return map;
2576
+ } catch (err) {
2577
+ console.error(
2578
+ `[wa-chatmeta] op=read outcome=fail account=${accountId.slice(0, 8)} reason=${(err instanceof Error ? err.message : String(err)).slice(0, 100)}`
2579
+ );
2580
+ return /* @__PURE__ */ new Map();
2581
+ }
2582
+ }
2583
+ function writeChatMetadataMerged(accountId, updates) {
2584
+ if (updates.length === 0) return -1;
2585
+ const path3 = chatMetadataPath(accountId);
2586
+ try {
2587
+ mkdirSync3(dirname2(path3), { recursive: true });
2588
+ let merged = /* @__PURE__ */ new Map();
2589
+ if (existsSync4(path3)) merged = parseChatMetadata(readFileSync5(path3, "utf8"));
2590
+ for (const entry2 of updates) merged.set(entry2.jid, preferEntry(merged.get(entry2.jid), entry2));
2591
+ const tmp = `${path3}.tmp`;
2592
+ writeFileSync3(tmp, serializeChatMetadata(merged), "utf8");
2593
+ renameSync(tmp, path3);
2594
+ return parseChatMetadata(readFileSync5(path3, "utf8")).size;
2595
+ } catch (err) {
2596
+ console.error(
2597
+ `[wa-chatmeta] op=persist outcome=fail account=${accountId.slice(0, 8)} reason=${(err instanceof Error ? err.message : String(err)).slice(0, 100)}`
2598
+ );
2599
+ return -1;
2600
+ }
2601
+ }
2602
+
2603
+ // app/lib/whatsapp/chat-metadata-events.ts
2604
+ var NON_CHAT_JIDS = /* @__PURE__ */ new Set(["status@broadcast"]);
2605
+ function isGroupJid2(jid) {
2606
+ return jid.endsWith("@g.us");
2607
+ }
2608
+ function entry(jid, name, source, nowIso) {
2609
+ return { jid, name, source, isGroup: isGroupJid2(jid), updatedAt: nowIso };
2610
+ }
2611
+ function conversationKey(jid, phoneNumber) {
2612
+ if (!jid.endsWith("@lid")) return jid;
2613
+ if (typeof phoneNumber !== "string") return jid;
2614
+ const trimmed = phoneNumber.trim();
2615
+ return trimmed.endsWith("@s.whatsapp.net") ? trimmed : jid;
2616
+ }
2617
+ function usable(jid, name, phoneNumber) {
2618
+ if (typeof jid !== "string" || jid.length === 0) return null;
2619
+ if (NON_CHAT_JIDS.has(jid)) return null;
2620
+ const trimmed = typeof name === "string" ? name.trim() : "";
2621
+ if (trimmed.length === 0) return null;
2622
+ return { jid: conversationKey(jid, phoneNumber), name: trimmed };
2623
+ }
2624
+ function chatEntriesFromChats(chats, nowIso) {
2625
+ const out = [];
2626
+ for (const c of chats) {
2627
+ const ok = usable(c.id, c.name, c.phoneNumber);
2628
+ if (ok) out.push(entry(ok.jid, ok.name, "chat", nowIso));
2629
+ }
2630
+ return out;
2631
+ }
2632
+ function chatEntriesFromContacts(contacts, nowIso) {
2633
+ const out = [];
2634
+ for (const c of contacts) {
2635
+ const ok = usable(c.id, c.name, c.phoneNumber);
2636
+ if (ok) out.push(entry(ok.jid, ok.name, isGroupJid2(ok.jid) ? "chat" : "contact", nowIso));
2637
+ }
2638
+ return out;
2639
+ }
2640
+ function groupEntriesFromUpdates(updates, nowIso) {
2641
+ const out = [];
2642
+ for (const g of updates) {
2643
+ const ok = usable(g.id, g.subject);
2644
+ if (ok) out.push(entry(ok.jid, ok.name, "group", nowIso));
2645
+ }
2646
+ return out;
2647
+ }
2648
+
2298
2649
  // app/lib/whatsapp/ensure-conversation.ts
2299
- var TAG10 = "[whatsapp-persist]";
2650
+ var TAG11 = "[whatsapp-persist]";
2300
2651
  async function ensureWhatsAppConversation(input) {
2301
2652
  const t0 = Date.now();
2302
2653
  try {
@@ -2308,27 +2659,27 @@ async function ensureWhatsAppConversation(input) {
2308
2659
  const ms = Date.now() - t0;
2309
2660
  if (!result.sessionId) {
2310
2661
  console.error(
2311
- `${TAG10} conversation-merged FAIL cacheKey=${input.cacheKey} reason=null-sessionId ms=${ms}`
2662
+ `${TAG11} conversation-merged FAIL cacheKey=${input.cacheKey} reason=null-sessionId ms=${ms}`
2312
2663
  );
2313
2664
  return null;
2314
2665
  }
2315
2666
  console.error(
2316
- `${TAG10} conversation-merged cacheKey=${input.cacheKey} agentType=${input.agentType} channel=whatsapp accountId=${input.platformAccountId} connectionAccountId=${input.connectionAccountId ?? input.platformAccountId} created=${result.created} ms=${ms}`
2667
+ `${TAG11} conversation-merged cacheKey=${input.cacheKey} agentType=${input.agentType} channel=whatsapp accountId=${input.platformAccountId} connectionAccountId=${input.connectionAccountId ?? input.platformAccountId} created=${result.created} ms=${ms}`
2317
2668
  );
2318
2669
  return { sessionId: result.sessionId, created: result.created };
2319
2670
  } catch (err) {
2320
2671
  const ms = Date.now() - t0;
2321
2672
  const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
2322
2673
  console.error(
2323
- `${TAG10} conversation-merged FAIL cacheKey=${input.cacheKey} reason=${reason} ms=${ms}`
2674
+ `${TAG11} conversation-merged FAIL cacheKey=${input.cacheKey} reason=${reason} ms=${ms}`
2324
2675
  );
2325
2676
  return null;
2326
2677
  }
2327
2678
  }
2328
2679
 
2329
2680
  // ../lib/account-enumeration/src/index.ts
2330
- import { readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
2331
- import { resolve as resolve2 } from "path";
2681
+ import { readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
2682
+ import { resolve as resolve3 } from "path";
2332
2683
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2333
2684
  var cache2 = /* @__PURE__ */ new Map();
2334
2685
  function enumerateValidAccountIds(accountsDir) {
@@ -2347,9 +2698,9 @@ function enumerateValidAccountIds(accountsDir) {
2347
2698
  const valid = [];
2348
2699
  for (const name of names) {
2349
2700
  if (!UUID_RE.test(name)) continue;
2350
- const configPath = resolve2(accountsDir, name, "account.json");
2701
+ const configPath = resolve3(accountsDir, name, "account.json");
2351
2702
  try {
2352
- JSON.parse(readFileSync5(configPath, "utf-8"));
2703
+ JSON.parse(readFileSync6(configPath, "utf-8"));
2353
2704
  valid.push(name);
2354
2705
  } catch (err) {
2355
2706
  const code = err.code;
@@ -2363,9 +2714,9 @@ function resolveHouseOrSoleAccountId(accountsDir) {
2363
2714
  const valid = enumerateValidAccountIds(accountsDir);
2364
2715
  const houses = [];
2365
2716
  for (const id of valid) {
2366
- const configPath = resolve2(accountsDir, id, "account.json");
2717
+ const configPath = resolve3(accountsDir, id, "account.json");
2367
2718
  try {
2368
- const cfg = JSON.parse(readFileSync5(configPath, "utf-8"));
2719
+ const cfg = JSON.parse(readFileSync6(configPath, "utf-8"));
2369
2720
  if (cfg.role === "house") houses.push(id);
2370
2721
  } catch {
2371
2722
  }
@@ -2383,7 +2734,7 @@ function getAccountsDirFromEnv() {
2383
2734
  "[graph-write] MAXY_PLATFORM_ROOT not set \u2014 cannot enforce accountId gate. Set MAXY_PLATFORM_ROOT in the spawning process or pass `accountsDir` explicitly."
2384
2735
  );
2385
2736
  }
2386
- return resolve2(root, "..", "data/accounts");
2737
+ return resolve3(root, "..", "data/accounts");
2387
2738
  }
2388
2739
  function validateAccountIdEnv(envValue, diskIds) {
2389
2740
  if (!envValue) {
@@ -2399,8 +2750,8 @@ function validateAccountIdEnv(envValue, diskIds) {
2399
2750
  }
2400
2751
 
2401
2752
  // app/lib/whatsapp/platform-account-id.ts
2402
- import { readFileSync as readFileSync6 } from "fs";
2403
- import { resolve as resolve3 } from "path";
2753
+ import { readFileSync as readFileSync7 } from "fs";
2754
+ import { resolve as resolve4 } from "path";
2404
2755
  var cachedHouseId = null;
2405
2756
  var cachedHouseAccountsDir = null;
2406
2757
  function resolveHouseId(accountsDir) {
@@ -2411,10 +2762,10 @@ function resolveHouseId(accountsDir) {
2411
2762
  return cachedHouseId;
2412
2763
  }
2413
2764
  function readChannelRoutingOverride(accountsDir, houseId) {
2414
- const configPath = resolve3(accountsDir, houseId, "account.json");
2765
+ const configPath = resolve4(accountsDir, houseId, "account.json");
2415
2766
  let raw;
2416
2767
  try {
2417
- raw = readFileSync6(configPath, "utf-8");
2768
+ raw = readFileSync7(configPath, "utf-8");
2418
2769
  } catch {
2419
2770
  return null;
2420
2771
  }
@@ -2455,8 +2806,8 @@ function warnNonUuidAccount(caller, accountId) {
2455
2806
  }
2456
2807
 
2457
2808
  // app/lib/managed-service.ts
2458
- import { join as join3 } from "path";
2459
- import { readFileSync as readFileSync7 } from "fs";
2809
+ import { join as join5 } from "path";
2810
+ import { readFileSync as readFileSync8 } from "fs";
2460
2811
  function isHouseManagedService(accountsDir = ACCOUNTS_DIR) {
2461
2812
  let houseId;
2462
2813
  try {
@@ -2466,7 +2817,7 @@ function isHouseManagedService(accountsDir = ACCOUNTS_DIR) {
2466
2817
  }
2467
2818
  try {
2468
2819
  const cfg = JSON.parse(
2469
- readFileSync7(join3(accountsDir, houseId, "account.json"), "utf-8")
2820
+ readFileSync8(join5(accountsDir, houseId, "account.json"), "utf-8")
2470
2821
  );
2471
2822
  return cfg.managedService === true;
2472
2823
  } catch {
@@ -2480,7 +2831,7 @@ function isCustomerTurnSuppressed(role, managedService) {
2480
2831
  // app/lib/whatsapp/inbound/media.ts
2481
2832
  import { randomUUID as randomUUID4, createHash } from "crypto";
2482
2833
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2483
- import { join as join5 } from "path";
2834
+ import { join as join6 } from "path";
2484
2835
  import {
2485
2836
  downloadMediaMessage,
2486
2837
  downloadContentFromMessage,
@@ -2490,85 +2841,9 @@ import {
2490
2841
  // app/lib/attachments.ts
2491
2842
  import { randomUUID as randomUUID3 } from "crypto";
2492
2843
  import { mkdir, writeFile } from "fs/promises";
2493
- import { realpathSync as realpathSync2, existsSync as existsSync4, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "fs";
2844
+ import { realpathSync as realpathSync2, existsSync as existsSync5, readFileSync as readFileSync9, readdirSync as readdirSync4 } from "fs";
2494
2845
  import { resolve as resolve5, extname } from "path";
2495
2846
 
2496
- // app/lib/data-path.ts
2497
- import { realpathSync } from "fs";
2498
- import { resolve as resolve4, normalize, sep, relative, basename, join as join4 } from "path";
2499
- var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "../platform");
2500
- var DATA_ROOT = resolve4(PLATFORM_ROOT2, "..", "data");
2501
- var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve4(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
2502
- var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["accounts"]);
2503
- var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2504
- function crossesForeignAccountPartition(relPath, accountId) {
2505
- const segs = relPath.split("/").filter(Boolean);
2506
- if (segs.length < 2) return false;
2507
- if (!ACCOUNT_PARTITION_DIRS.has(segs[0])) return false;
2508
- const owner = segs[1];
2509
- if (!ACCOUNT_UUID_RE.test(owner)) return false;
2510
- return owner !== accountId;
2511
- }
2512
- function isWithinOwnAccountPartition(relPath, accountId) {
2513
- const segs = relPath.split("/").filter(Boolean);
2514
- return segs.length >= 2 && segs[0] === "accounts" && segs[1] === accountId;
2515
- }
2516
- function resolveUnderRoot(raw, rootAbs, rootLabel) {
2517
- const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
2518
- const absolute = resolve4(rootAbs, cleaned);
2519
- let rootReal;
2520
- try {
2521
- rootReal = realpathSync(rootAbs);
2522
- } catch (err) {
2523
- return {
2524
- ok: false,
2525
- status: 500,
2526
- error: `${rootLabel} not accessible: ${err instanceof Error ? err.message : String(err)}`
2527
- };
2528
- }
2529
- let resolvedReal;
2530
- try {
2531
- resolvedReal = realpathSync(absolute);
2532
- } catch (err) {
2533
- const code = err.code;
2534
- if (code === "ENOENT") {
2535
- if (absolute !== rootReal && !absolute.startsWith(rootReal + sep)) {
2536
- return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: absolute };
2537
- }
2538
- return { ok: false, status: 404, error: "Not found" };
2539
- }
2540
- return {
2541
- ok: false,
2542
- status: 500,
2543
- error: err instanceof Error ? err.message : String(err)
2544
- };
2545
- }
2546
- if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + sep)) {
2547
- return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: resolvedReal };
2548
- }
2549
- const relPath = relative(rootReal, resolvedReal) || ".";
2550
- return { ok: true, absolute: resolvedReal, dataRootReal: rootReal, relative: relPath };
2551
- }
2552
- function resolveDataPath(raw) {
2553
- return resolveUnderRoot(raw, DATA_ROOT, "DATA_ROOT");
2554
- }
2555
- function resolveClaudeUploadsPath(raw) {
2556
- if (!CLAUDE_UPLOADS_ROOT) {
2557
- return { ok: false, status: 404, error: "Not found" };
2558
- }
2559
- return resolveUnderRoot(raw, CLAUDE_UPLOADS_ROOT, "CLAUDE_UPLOADS_ROOT");
2560
- }
2561
- function scratchpadTmpBase() {
2562
- return process.env.MAXY_SCRATCHPAD_BASE ?? "/tmp";
2563
- }
2564
- function sessionScratchpadRoot(projectDir, sessionId) {
2565
- const uid = typeof process.geteuid === "function" ? process.geteuid() : 0;
2566
- return join4(scratchpadTmpBase(), `claude-${uid}`, basename(projectDir), sessionId, "scratchpad");
2567
- }
2568
- function resolveScratchpadPath(raw, scratchpadRootAbs) {
2569
- return resolveUnderRoot(raw, scratchpadRootAbs, "scratchpad");
2570
- }
2571
-
2572
2847
  // app/lib/doc-to-text.ts
2573
2848
  import { unzipSync, strFromU8 } from "fflate";
2574
2849
  var DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
@@ -2589,10 +2864,10 @@ function bareMime(mimeType) {
2589
2864
  function unescapeXml(s) {
2590
2865
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
2591
2866
  }
2592
- function unzipEntry(buffer, entry) {
2867
+ function unzipEntry(buffer, entry2) {
2593
2868
  const files = unzipSync(new Uint8Array(buffer));
2594
- const bytes = files[entry];
2595
- if (!bytes) throw new Error(`zip entry not found: ${entry}`);
2869
+ const bytes = files[entry2];
2870
+ if (!bytes) throw new Error(`zip entry not found: ${entry2}`);
2596
2871
  return strFromU8(bytes);
2597
2872
  }
2598
2873
  function docxToText(buffer) {
@@ -2814,14 +3089,14 @@ async function storeMediaBuffer(accountId, attachmentId, filename, mimeType, buf
2814
3089
  return writeAttachment(accountId, "", filename, mimeType, buffer.byteLength, buffer, attachmentId);
2815
3090
  }
2816
3091
  function attachmentExists(accountId, attachmentId) {
2817
- return existsSync4(resolve5(uploadsDirFor(accountId, "", attachmentId), `${attachmentId}.meta.json`));
3092
+ return existsSync5(resolve5(uploadsDirFor(accountId, "", attachmentId), `${attachmentId}.meta.json`));
2818
3093
  }
2819
3094
  function resolveStoredAttachment(accountId, attachmentId) {
2820
3095
  if (!ATTACHMENT_ID_RE.test(attachmentId)) return null;
2821
3096
  const dir = uploadsDirFor(accountId, "", attachmentId);
2822
3097
  let meta;
2823
3098
  try {
2824
- meta = JSON.parse(readFileSync8(resolve5(dir, `${attachmentId}.meta.json`), "utf8"));
3099
+ meta = JSON.parse(readFileSync9(resolve5(dir, `${attachmentId}.meta.json`), "utf8"));
2825
3100
  } catch {
2826
3101
  return null;
2827
3102
  }
@@ -2969,7 +3244,7 @@ async function storeComponentArtefact(accountId, attachmentId, mimeType, content
2969
3244
  }
2970
3245
 
2971
3246
  // app/lib/whatsapp/inbound/media-download-guard.ts
2972
- var TAG11 = "[media-guard]";
3247
+ var TAG12 = "[media-guard]";
2973
3248
  var inFlight = 0;
2974
3249
  var containedCount = 0;
2975
3250
  function containedSinceBoot() {
@@ -2999,13 +3274,13 @@ function handleEscapedMediaError(err, channel) {
2999
3274
  if (undiciFrame && inFlight > 0) {
3000
3275
  containedCount += 1;
3001
3276
  console.error(
3002
- `${TAG11} op=contained channel=${channel} inFlight=${inFlight} err=${err instanceof Error ? err.message : String(err)} cause=${causeCode(err)}
3277
+ `${TAG12} op=contained channel=${channel} inFlight=${inFlight} err=${err instanceof Error ? err.message : String(err)} cause=${causeCode(err)}
3003
3278
  ${stack}`
3004
3279
  );
3005
3280
  return true;
3006
3281
  }
3007
3282
  console.error(
3008
- `${TAG11} op=escaped channel=${channel} inFlight=${inFlight} undiciFrame=${undiciFrame} err=${err instanceof Error ? err.message : String(err)} cause=${causeCode(err)} contained=false`
3283
+ `${TAG12} op=escaped channel=${channel} inFlight=${inFlight} undiciFrame=${undiciFrame} err=${err instanceof Error ? err.message : String(err)} cause=${causeCode(err)} contained=false`
3009
3284
  );
3010
3285
  return false;
3011
3286
  }
@@ -3030,11 +3305,11 @@ function installMediaDownloadGuard() {
3030
3305
  );
3031
3306
  process.exit(1);
3032
3307
  });
3033
- console.error(`${TAG11} op=installed`);
3308
+ console.error(`${TAG12} op=installed`);
3034
3309
  }
3035
3310
 
3036
3311
  // app/lib/whatsapp/inbound/media.ts
3037
- var TAG12 = "[whatsapp:media]";
3312
+ var TAG13 = "[whatsapp:media]";
3038
3313
  var MEDIA_DIR = "/tmp/maxy-media";
3039
3314
  var DOWNLOAD_TIMEOUT_MS = 45e3;
3040
3315
  var DETACHED_GRACE_MS = 3e5;
@@ -3124,25 +3399,25 @@ async function downloadInboundMediaBuffer(msg, sock, opts) {
3124
3399
  }
3125
3400
  );
3126
3401
  if (!buffer || buffer.length === 0) {
3127
- console.error(`${TAG12} primary download returned empty, trying direct fallback`);
3402
+ console.error(`${TAG13} primary download returned empty, trying direct fallback`);
3128
3403
  const downloadable = getDownloadableContent(content);
3129
3404
  if (downloadable) {
3130
3405
  try {
3131
3406
  const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
3132
3407
  buffer = await streamToBuffer(stream);
3133
3408
  } catch (fallbackErr) {
3134
- console.error(`${TAG12} direct download fallback failed: ${String(fallbackErr)}`);
3409
+ console.error(`${TAG13} direct download fallback failed: ${String(fallbackErr)}`);
3135
3410
  }
3136
3411
  }
3137
3412
  }
3138
3413
  if (!buffer || buffer.length === 0) {
3139
- console.error(`${TAG12} download failed: empty buffer for ${mimetype ?? "unknown"}`);
3414
+ console.error(`${TAG13} download failed: empty buffer for ${mimetype ?? "unknown"}`);
3140
3415
  return void 0;
3141
3416
  }
3142
3417
  if (buffer.length > maxBytes) {
3143
3418
  const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
3144
3419
  const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
3145
- console.error(`${TAG12} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
3420
+ console.error(`${TAG13} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
3146
3421
  return void 0;
3147
3422
  }
3148
3423
  return {
@@ -3156,7 +3431,7 @@ async function downloadInboundMediaBuffer(msg, sock, opts) {
3156
3431
  endMediaDownload();
3157
3432
  return settled;
3158
3433
  } catch (err) {
3159
- console.error(`${TAG12} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
3434
+ console.error(`${TAG13} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
3160
3435
  if (isTimeoutError(err)) {
3161
3436
  const t = setTimeout(endMediaDownload, DETACHED_GRACE_MS);
3162
3437
  t.unref?.();
@@ -3171,10 +3446,10 @@ async function downloadInboundMedia(msg, sock, opts) {
3171
3446
  if (!res) return void 0;
3172
3447
  await mkdir2(MEDIA_DIR, { recursive: true });
3173
3448
  const ext = mimeToExt(res.mimetype);
3174
- const filePath = join5(MEDIA_DIR, `${randomUUID4()}.${ext}`);
3449
+ const filePath = join6(MEDIA_DIR, `${randomUUID4()}.${ext}`);
3175
3450
  await writeFile2(filePath, res.buffer);
3176
3451
  const sizeKB = (res.buffer.length / 1024).toFixed(0);
3177
- console.error(`${TAG12} media downloaded type=${res.mimetype} size=${sizeKB}KB path=${filePath}`);
3452
+ console.error(`${TAG13} media downloaded type=${res.mimetype} size=${sizeKB}KB path=${filePath}`);
3178
3453
  return { path: filePath, mimetype: res.mimetype, size: res.buffer.length };
3179
3454
  }
3180
3455
  async function storeInboundMediaServable(msg, sock, accountId, seed, opts) {
@@ -3183,15 +3458,15 @@ async function storeInboundMediaServable(msg, sock, accountId, seed, opts) {
3183
3458
  if (attachmentExists(accountId, attachmentId)) return attachmentId;
3184
3459
  const res = await downloadInboundMediaBufferCached(msg, sock, opts);
3185
3460
  if (!res) {
3186
- console.error(`${TAG12} servable-store skip reason=no-buffer id=${attachmentId}`);
3461
+ console.error(`${TAG13} servable-store skip reason=no-buffer id=${attachmentId}`);
3187
3462
  return null;
3188
3463
  }
3189
3464
  const filename = res.filename ?? `${attachmentId}.${mimeToExt(res.mimetype)}`;
3190
3465
  await storeMediaBuffer(accountId, attachmentId, filename, res.mimetype, res.buffer);
3191
- console.error(`${TAG12} servable-store ok id=${attachmentId} mime=${res.mimetype} bytes=${res.buffer.length}`);
3466
+ console.error(`${TAG13} servable-store ok id=${attachmentId} mime=${res.mimetype} bytes=${res.buffer.length}`);
3192
3467
  return attachmentId;
3193
3468
  } catch (err) {
3194
- console.error(`${TAG12} servable-store FAIL id=${attachmentId} reason=${String(err).slice(0, 120)}`);
3469
+ console.error(`${TAG13} servable-store FAIL id=${attachmentId} reason=${String(err).slice(0, 120)}`);
3195
3470
  return null;
3196
3471
  }
3197
3472
  }
@@ -3239,7 +3514,7 @@ function createBoundedQueue(limit, opts) {
3239
3514
  }
3240
3515
 
3241
3516
  // app/lib/whatsapp/inbound/debounce.ts
3242
- var TAG13 = "[whatsapp:debounce]";
3517
+ var TAG14 = "[whatsapp:debounce]";
3243
3518
  var STT_TAG = "[whatsapp:stt-await]";
3244
3519
  function createInboundDebouncer(opts) {
3245
3520
  const { debounceMs, buildKey, onFlush, onError } = opts;
@@ -3270,7 +3545,7 @@ function createInboundDebouncer(opts) {
3270
3545
  pending.delete(key);
3271
3546
  const batchSize = batch.entries.length;
3272
3547
  try {
3273
- console.error(`${TAG13} debounce flush key=${key} batchSize=${batchSize}`);
3548
+ console.error(`${TAG14} debounce flush key=${key} batchSize=${batchSize}`);
3274
3549
  const result = onFlush(batch.entries);
3275
3550
  if (result && typeof result.catch === "function") {
3276
3551
  result.catch(onError);
@@ -3350,7 +3625,7 @@ function combineInboundBatch(entries) {
3350
3625
  }
3351
3626
 
3352
3627
  // app/lib/whatsapp/opening-hours.ts
3353
- var TAG14 = "[whatsapp:hours]";
3628
+ var TAG15 = "[whatsapp:hours]";
3354
3629
  async function isBusinessOpen(accountId) {
3355
3630
  try {
3356
3631
  const timezone = await resolveTimezone(accountId);
@@ -3368,7 +3643,7 @@ async function isBusinessOpen(accountId) {
3368
3643
  { accountId, dayOfWeek, previousDayOfWeek }
3369
3644
  );
3370
3645
  if (result.records.length === 0) {
3371
- console.error(`${TAG14} [${accountId}] business hours check: no opening hours configured, treating as open`);
3646
+ console.error(`${TAG15} [${accountId}] business hours check: no opening hours configured, treating as open`);
3372
3647
  return { open: true, reason: "no opening hours configured" };
3373
3648
  }
3374
3649
  const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
@@ -3377,7 +3652,7 @@ async function isBusinessOpen(accountId) {
3377
3652
  closes: String(r.get("closes")).trim()
3378
3653
  }));
3379
3654
  if (specs.length === 0) {
3380
- console.error(`${TAG14} [${accountId}] business hours check: no opening hours configured, treating as open`);
3655
+ console.error(`${TAG15} [${accountId}] business hours check: no opening hours configured, treating as open`);
3381
3656
  return { open: true, reason: "no opening hours configured" };
3382
3657
  }
3383
3658
  for (const spec of specs) {
@@ -3387,7 +3662,7 @@ async function isBusinessOpen(accountId) {
3387
3662
  if (spec.opens > spec.closes && currentTime < spec.closes) {
3388
3663
  const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
3389
3664
  console.error(
3390
- `${TAG14} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
3665
+ `${TAG15} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
3391
3666
  );
3392
3667
  return {
3393
3668
  open: true,
@@ -3400,7 +3675,7 @@ async function isBusinessOpen(accountId) {
3400
3675
  } else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
3401
3676
  const hoursStr = `${spec.opens}-${spec.closes}`;
3402
3677
  console.error(
3403
- `${TAG14} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
3678
+ `${TAG15} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
3404
3679
  );
3405
3680
  return {
3406
3681
  open: true,
@@ -3414,7 +3689,7 @@ async function isBusinessOpen(accountId) {
3414
3689
  const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
3415
3690
  const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
3416
3691
  console.error(
3417
- `${TAG14} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
3692
+ `${TAG15} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
3418
3693
  );
3419
3694
  return {
3420
3695
  open: false,
@@ -3428,7 +3703,7 @@ async function isBusinessOpen(accountId) {
3428
3703
  }
3429
3704
  } catch (err) {
3430
3705
  console.error(
3431
- `${TAG14} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
3706
+ `${TAG15} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
3432
3707
  );
3433
3708
  return { open: true, reason: "hours check failed (treating as open)" };
3434
3709
  }
@@ -3474,7 +3749,7 @@ import { execFile } from "child_process";
3474
3749
  import { unlink, stat } from "fs/promises";
3475
3750
  import { promisify } from "util";
3476
3751
  var execFileAsync = promisify(execFile);
3477
- var TAG15 = "[stt]";
3752
+ var TAG16 = "[stt]";
3478
3753
  var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
3479
3754
  var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
3480
3755
  var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
@@ -3493,11 +3768,11 @@ async function transcribe(audioPath, mimetype) {
3493
3768
  const s = await stat(audioPath);
3494
3769
  audioBytes = s.size;
3495
3770
  } catch {
3496
- console.error(`${TAG15} failed: file not readable path=${audioPath}`);
3771
+ console.error(`${TAG16} failed: file not readable path=${audioPath}`);
3497
3772
  return void 0;
3498
3773
  }
3499
3774
  console.error(
3500
- `${TAG15} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
3775
+ `${TAG16} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
3501
3776
  );
3502
3777
  const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
3503
3778
  try {
@@ -3516,7 +3791,7 @@ async function transcribe(audioPath, mimetype) {
3516
3791
  ], { timeout: 3e4 });
3517
3792
  } catch (err) {
3518
3793
  const reason = err instanceof Error ? err.message : String(err);
3519
- console.error(`${TAG15} failed: ffmpeg conversion error=${reason}`);
3794
+ console.error(`${TAG16} failed: ffmpeg conversion error=${reason}`);
3520
3795
  return void 0;
3521
3796
  }
3522
3797
  try {
@@ -3532,20 +3807,20 @@ async function transcribe(audioPath, mimetype) {
3532
3807
  const durationMs = Date.now() - startMs;
3533
3808
  const { text, count: stripped } = stripSpecialTokens(stdout.trim());
3534
3809
  if (!text) {
3535
- console.error(`${TAG15} failed: whisper returned empty output duration_ms=${durationMs}`);
3810
+ console.error(`${TAG16} failed: whisper returned empty output duration_ms=${durationMs}`);
3536
3811
  return void 0;
3537
3812
  }
3538
3813
  const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
3539
3814
  const language = langMatch?.[1] ?? "unknown";
3540
3815
  const words = text.split(/\s+/).filter(Boolean).length;
3541
3816
  console.error(
3542
- `${TAG15} done provider=whisper-local duration_ms=${durationMs} words=${words} stripped=${stripped} lang=${language}`
3817
+ `${TAG16} done provider=whisper-local duration_ms=${durationMs} words=${words} stripped=${stripped} lang=${language}`
3543
3818
  );
3544
3819
  return { text, language, durationMs };
3545
3820
  } catch (err) {
3546
3821
  const durationMs = Date.now() - startMs;
3547
3822
  const reason = err instanceof Error ? err.message : String(err);
3548
- console.error(`${TAG15} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
3823
+ console.error(`${TAG16} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
3549
3824
  return void 0;
3550
3825
  } finally {
3551
3826
  unlink(wavPath).catch(() => {
@@ -3564,8 +3839,9 @@ var WaReplyError = class extends Error {
3564
3839
  };
3565
3840
 
3566
3841
  // app/lib/whatsapp/manager.ts
3567
- var TAG16 = "[whatsapp:manager]";
3842
+ var TAG17 = "[whatsapp:manager]";
3568
3843
  var MAX_RECONNECT_ATTEMPTS = 10;
3844
+ var REARM_INTERVAL_MS = 15 * 6e4;
3569
3845
  var MESSAGE_STORE_MAX = 500;
3570
3846
  var APPEND_MEDIA_CONCURRENCY = 3;
3571
3847
  var APPEND_MEDIA_MAX_PENDING = 24;
@@ -3607,17 +3883,17 @@ var messageStore = /* @__PURE__ */ new Map();
3607
3883
  function messageStoreKey(accountId, remoteJid) {
3608
3884
  return `${accountId}:${remoteJid}`;
3609
3885
  }
3610
- function storeMessage(storeKey, entry) {
3886
+ function storeMessage(storeKey, entry2) {
3611
3887
  let entries = messageStore.get(storeKey);
3612
3888
  if (!entries) {
3613
3889
  entries = [];
3614
3890
  messageStore.set(storeKey, entries);
3615
3891
  }
3616
- entries.push(entry);
3892
+ entries.push(entry2);
3617
3893
  if (entries.length > MESSAGE_STORE_MAX) {
3618
3894
  const trimmed = entries.length - MESSAGE_STORE_MAX;
3619
3895
  entries.splice(0, trimmed);
3620
- console.error(`${TAG16} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
3896
+ console.error(`${TAG17} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
3621
3897
  }
3622
3898
  }
3623
3899
  function deriveCacheKey(input) {
@@ -3661,7 +3937,7 @@ function buildSelfChatDispatch(input) {
3661
3937
  }
3662
3938
  async function init(opts) {
3663
3939
  if (initialized) {
3664
- console.error(`${TAG16} already initialized`);
3940
+ console.error(`${TAG17} already initialized`);
3665
3941
  return;
3666
3942
  }
3667
3943
  configDir = opts.configDir;
@@ -3671,20 +3947,20 @@ async function init(opts) {
3671
3947
  loadConfig(opts.accountConfig);
3672
3948
  const accountIds = listCredentialAccountIds(configDir);
3673
3949
  if (accountIds.length === 0) {
3674
- console.error(`${TAG16} init: no stored WhatsApp credentials found`);
3950
+ console.error(`${TAG17} init: no stored WhatsApp credentials found`);
3675
3951
  initialized = true;
3676
3952
  return;
3677
3953
  }
3678
- console.error(`${TAG16} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
3954
+ console.error(`${TAG17} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
3679
3955
  initialized = true;
3680
3956
  for (const accountId of accountIds) {
3681
3957
  const accountCfg = whatsAppConfig.accounts?.[accountId];
3682
3958
  if (accountCfg?.enabled === false) {
3683
- console.error(`${TAG16} skipping disabled account=${accountId}`);
3959
+ console.error(`${TAG17} skipping disabled account=${accountId}`);
3684
3960
  continue;
3685
3961
  }
3686
3962
  startConnection(accountId).catch((err) => {
3687
- console.error(`${TAG16} failed to auto-start account=${accountId}: ${formatError(err)}`);
3963
+ console.error(`${TAG17} failed to auto-start account=${accountId}: ${formatError(err)}`);
3688
3964
  });
3689
3965
  }
3690
3966
  }
@@ -3693,7 +3969,7 @@ async function startConnection(accountId) {
3693
3969
  const authDir = resolveAuthDir(configDir, accountId);
3694
3970
  const hasAuth = await authExists(authDir);
3695
3971
  if (!hasAuth) {
3696
- console.error(`${TAG16} no credentials for account=${accountId}`);
3972
+ console.error(`${TAG17} no credentials for account=${accountId}`);
3697
3973
  return;
3698
3974
  }
3699
3975
  let platformAccountId;
@@ -3724,6 +4000,7 @@ async function startConnection(accountId) {
3724
4000
  selfJid: null,
3725
4001
  selfLid: null,
3726
4002
  reconnectAttempts: 0,
4003
+ rearmAttempts: 0,
3727
4004
  abortController: new AbortController(),
3728
4005
  debouncer: null,
3729
4006
  lidMapping: null
@@ -3747,11 +4024,11 @@ async function stopConnection(accountId) {
3747
4024
  conn.sock.ev.removeAllListeners("creds.update");
3748
4025
  conn.sock.ws?.close?.();
3749
4026
  } catch (err) {
3750
- console.warn(`${TAG16} socket cleanup error during stop account=${accountId}: ${String(err)}`);
4027
+ console.warn(`${TAG17} socket cleanup error during stop account=${accountId}: ${String(err)}`);
3751
4028
  }
3752
4029
  }
3753
4030
  connections.delete(accountId);
3754
- console.error(`${TAG16} stopped account=${accountId}`);
4031
+ console.error(`${TAG17} stopped account=${accountId}`);
3755
4032
  }
3756
4033
  function getStatus() {
3757
4034
  return Array.from(connections.values()).map((conn) => ({
@@ -3862,6 +4139,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
3862
4139
  selfJid: null,
3863
4140
  selfLid: null,
3864
4141
  reconnectAttempts: 0,
4142
+ rearmAttempts: 0,
3865
4143
  lastConnectedAt: Date.now(),
3866
4144
  abortController: new AbortController(),
3867
4145
  debouncer: null,
@@ -3870,11 +4148,12 @@ async function registerLoginSocket(accountId, sock, authDir) {
3870
4148
  applySelfIdentity(conn, sock, "register");
3871
4149
  connections.set(accountId, conn);
3872
4150
  monitorInbound(conn);
4151
+ monitorChatMetadata(conn);
3873
4152
  try {
3874
4153
  await sock.sendPresenceUpdate("available");
3875
- console.error(`${TAG16} presence set to available account=${accountId}`);
4154
+ console.error(`${TAG17} presence set to available account=${accountId}`);
3876
4155
  } catch (err) {
3877
- console.error(`${TAG16} presence update failed account=${accountId}: ${String(err)}`);
4156
+ console.error(`${TAG17} presence update failed account=${accountId}: ${String(err)}`);
3878
4157
  }
3879
4158
  await runInitQueries(sock, {
3880
4159
  accountId,
@@ -3885,7 +4164,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
3885
4164
  });
3886
4165
  watchForDisconnect(conn);
3887
4166
  attachSelfIdRefreshOnCredsUpdate(conn);
3888
- console.error(`${TAG16} registered login socket for account=${accountId} phone=${conn.selfPhone ?? "unknown"}`);
4167
+ console.error(`${TAG17} registered login socket for account=${accountId} phone=${conn.selfPhone ?? "unknown"}`);
3889
4168
  }
3890
4169
  function attachSelfIdRefreshOnCredsUpdate(conn) {
3891
4170
  if (!conn.sock) return;
@@ -3944,7 +4223,7 @@ async function shutdown() {
3944
4223
  const ids = Array.from(connections.keys());
3945
4224
  await Promise.all(ids.map((id) => stopConnection(id)));
3946
4225
  initialized = false;
3947
- console.error(`${TAG16} shutdown complete`);
4226
+ console.error(`${TAG17} shutdown complete`);
3948
4227
  }
3949
4228
  function loadConfig(accountConfig) {
3950
4229
  try {
@@ -3955,30 +4234,43 @@ function loadConfig(accountConfig) {
3955
4234
  if (result.ok) {
3956
4235
  whatsAppConfig = result.data;
3957
4236
  if (result.droppedKeys.length > 0) {
3958
- console.error(`${TAG16} config: dropped unknown keys=[${result.droppedKeys.join(",")}] preserved known config`);
4237
+ console.error(`${TAG17} config: dropped unknown keys=[${result.droppedKeys.join(",")}] preserved known config`);
3959
4238
  }
3960
4239
  } else {
3961
- console.error(`${TAG16} config validation failed: ${result.error.message}`);
4240
+ console.error(`${TAG17} config validation failed: ${result.error.message}`);
3962
4241
  whatsAppConfig = {};
3963
4242
  }
3964
4243
  const diskAdmin = Array.isArray(wa.adminPhones) ? wa.adminPhones : [];
3965
4244
  if (diskAdmin.length > 0 && (whatsAppConfig.adminPhones?.length ?? 0) === 0) {
3966
- console.error(`${TAG16} WARN adminPhones present on disk but empty after load`);
4245
+ console.error(`${TAG17} WARN adminPhones present on disk but empty after load`);
3967
4246
  }
3968
4247
  }
3969
4248
  } catch (err) {
3970
- console.error(`${TAG16} config load error: ${String(err)}`);
4249
+ console.error(`${TAG17} config load error: ${String(err)}`);
3971
4250
  whatsAppConfig = {};
3972
4251
  }
3973
4252
  }
4253
+ function sinceStuckMs(conn) {
4254
+ return conn.stuckSinceMs === void 0 ? 0 : Date.now() - conn.stuckSinceMs;
4255
+ }
4256
+ function clearStuck(conn) {
4257
+ if (!conn.sessionStuckReason) return;
4258
+ console.error(
4259
+ `${HEALTH_TAG} op=rearm account=${conn.accountId} attempt=${conn.rearmAttempts} sinceStuckMs=${sinceStuckMs(conn)} outcome=connected`
4260
+ );
4261
+ conn.sessionStuckReason = void 0;
4262
+ conn.stuckSinceMs = void 0;
4263
+ conn.rearmAttempts = 0;
4264
+ }
3974
4265
  async function connectWithReconnect(conn) {
3975
4266
  const maxAttempts = MAX_RECONNECT_ATTEMPTS;
3976
4267
  while (!conn.abortController.signal.aborted) {
3977
4268
  let uptimeMs = 0;
3978
4269
  let cycleError = null;
3979
4270
  let connectedAt;
4271
+ let heldTimer;
3980
4272
  try {
3981
- console.error(`${TAG16} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
4273
+ console.error(`${TAG17} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
3982
4274
  if (conn.debouncer) {
3983
4275
  await conn.debouncer.destroy();
3984
4276
  conn.debouncer = null;
@@ -3991,7 +4283,8 @@ async function connectWithReconnect(conn) {
3991
4283
  conn.sock = sock;
3992
4284
  applySelfIdentity(conn, sock, "pre-listener");
3993
4285
  monitorInbound(conn);
3994
- console.error(`${TAG16} socket created account=${conn.accountId} \u2014 waiting for connection`);
4286
+ monitorChatMetadata(conn);
4287
+ console.error(`${TAG17} socket created account=${conn.accountId} \u2014 waiting for connection`);
3995
4288
  await waitForConnection(sock);
3996
4289
  connectedAt = Date.now();
3997
4290
  conn.connected = true;
@@ -3999,12 +4292,19 @@ async function connectWithReconnect(conn) {
3999
4292
  conn.lastError = void 0;
4000
4293
  conn.terminalReason = void 0;
4001
4294
  applySelfIdentity(conn, sock, "post-connect");
4002
- console.error(`${TAG16} connected account=${conn.accountId} phone=${conn.selfPhone ?? "unknown"}`);
4295
+ console.error(`${TAG17} connected account=${conn.accountId} phone=${conn.selfPhone ?? "unknown"}`);
4296
+ if (conn.sessionStuckReason) {
4297
+ heldTimer = setTimeout(() => {
4298
+ if (!conn.connected || connections.get(conn.accountId) !== conn) return;
4299
+ clearStuck(conn);
4300
+ }, STABLE_UPTIME_MS);
4301
+ if (typeof heldTimer.unref === "function") heldTimer.unref();
4302
+ }
4003
4303
  try {
4004
4304
  await sock.sendPresenceUpdate("available");
4005
- console.error(`${TAG16} presence set to available account=${conn.accountId}`);
4305
+ console.error(`${TAG17} presence set to available account=${conn.accountId}`);
4006
4306
  } catch (err) {
4007
- console.error(`${TAG16} presence update failed account=${conn.accountId}: ${String(err)}`);
4307
+ console.error(`${TAG17} presence update failed account=${conn.accountId}: ${String(err)}`);
4008
4308
  }
4009
4309
  await runInitQueries(sock, {
4010
4310
  accountId: conn.accountId,
@@ -4027,13 +4327,20 @@ async function connectWithReconnect(conn) {
4027
4327
  }
4028
4328
  const classification = classifyDisconnect(err);
4029
4329
  conn.lastError = classification.message;
4030
- console.error(`${TAG16} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
4330
+ console.error(`${TAG17} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
4031
4331
  if (!classification.shouldRetry) {
4032
4332
  conn.terminalReason = classification.message;
4033
- console.error(`${TAG16} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
4333
+ if (conn.sessionStuckReason) {
4334
+ console.error(
4335
+ `${HEALTH_TAG} op=rearm account=${conn.accountId} attempt=${conn.rearmAttempts} sinceStuckMs=${sinceStuckMs(conn)} outcome=skipped-terminal`
4336
+ );
4337
+ }
4338
+ console.error(`${TAG17} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
4339
+ if (heldTimer !== void 0) clearTimeout(heldTimer);
4034
4340
  return;
4035
4341
  }
4036
4342
  }
4343
+ if (heldTimer !== void 0) clearTimeout(heldTimer);
4037
4344
  const decision = computeReconnectNextState({
4038
4345
  reconnectAttempts: conn.reconnectAttempts,
4039
4346
  uptimeMs,
@@ -4041,28 +4348,44 @@ async function connectWithReconnect(conn) {
4041
4348
  maxAttempts
4042
4349
  });
4043
4350
  if (decision.action === "abort") {
4044
- const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
4351
+ const stuckReason = `RETRYING SLOWLY account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} rearmIntervalMs=${REARM_INTERVAL_MS} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
4352
+ if (conn.rearmAttempts > 0) {
4353
+ console.error(
4354
+ `${HEALTH_TAG} op=rearm account=${conn.accountId} attempt=${conn.rearmAttempts} sinceStuckMs=${sinceStuckMs(conn)} outcome=failed`
4355
+ );
4356
+ }
4045
4357
  console.error(
4046
- `${TAG16} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
4358
+ `${TAG17} ${stuckReason} \u2014 re-arming in ${REARM_INTERVAL_MS}ms and every ${REARM_INTERVAL_MS}ms after; a re-pair via QR ends the wait sooner`
4047
4359
  );
4048
4360
  conn.sessionStuckReason = stuckReason;
4049
4361
  conn.lastError = stuckReason;
4050
- return;
4362
+ if (conn.stuckSinceMs === void 0) conn.stuckSinceMs = Date.now();
4363
+ await new Promise((resolve6) => {
4364
+ const timer = setTimeout(resolve6, REARM_INTERVAL_MS);
4365
+ conn.abortController.signal.addEventListener("abort", () => {
4366
+ clearTimeout(timer);
4367
+ resolve6();
4368
+ }, { once: true });
4369
+ });
4370
+ if (conn.abortController.signal.aborted) return;
4371
+ conn.rearmAttempts += 1;
4372
+ continue;
4051
4373
  }
4052
4374
  conn.reconnectAttempts = decision.nextAttempts;
4053
4375
  if (decision.reason === "short-lived") {
4054
4376
  console.error(
4055
- `${TAG16} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
4377
+ `${TAG17} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
4056
4378
  );
4057
4379
  } else {
4380
+ clearStuck(conn);
4058
4381
  console.error(
4059
- `${TAG16} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
4382
+ `${TAG17} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
4060
4383
  );
4061
4384
  }
4062
4385
  if (decision.reason === "short-lived" || cycleError) {
4063
4386
  const delay = computeBackoff(Math.max(1, decision.nextAttempts));
4064
4387
  console.error(
4065
- `${TAG16} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
4388
+ `${TAG17} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
4066
4389
  );
4067
4390
  await new Promise((resolve6) => {
4068
4391
  const timer = setTimeout(resolve6, delay);
@@ -4096,11 +4419,11 @@ function watchForDisconnect(conn) {
4096
4419
  conn.sock.ev.on("connection.update", (update) => {
4097
4420
  if (update.connection === "close") {
4098
4421
  if (connections.get(conn.accountId) !== conn) return;
4099
- console.error(`${TAG16} socket disconnected for account=${conn.accountId}`);
4422
+ console.error(`${TAG17} socket disconnected for account=${conn.accountId}`);
4100
4423
  conn.connected = false;
4101
4424
  conn.sock = null;
4102
4425
  connectWithReconnect(conn).catch((err) => {
4103
- console.error(`${TAG16} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
4426
+ console.error(`${TAG17} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
4104
4427
  });
4105
4428
  }
4106
4429
  });
@@ -4122,26 +4445,115 @@ function applySelfIdentity(conn, sock, phase) {
4122
4445
  );
4123
4446
  }
4124
4447
  }
4448
+ function monitorChatMetadata(conn) {
4449
+ if (!conn.sock) return;
4450
+ const sock = conn.sock;
4451
+ const account = conn.platformAccountId;
4452
+ const canonicalise = async (entries) => {
4453
+ const out = [];
4454
+ for (const e of entries) {
4455
+ if (!e.jid.endsWith("@lid")) {
4456
+ out.push(e);
4457
+ continue;
4458
+ }
4459
+ const { canonical, resolved } = await resolveCanonicalConversationJid(e.jid, conn.lidMapping);
4460
+ if (resolved === "lid2pn") {
4461
+ console.error(`${TAG17} [wa-chatmeta] op=canonical raw=${e.jid} canonical=${canonical}`);
4462
+ out.push({ ...e, jid: canonical });
4463
+ } else {
4464
+ out.push(e);
4465
+ }
4466
+ }
4467
+ return out;
4468
+ };
4469
+ const persist = (kind, entries, perJid) => {
4470
+ if (entries.length === 0) return;
4471
+ if (perJid) {
4472
+ for (const e of entries) {
4473
+ console.error(
4474
+ `${TAG17} [wa-chatmeta] op=event kind=${kind} jid=${e.jid} name=${e.name.length} source=${e.source}`
4475
+ );
4476
+ }
4477
+ }
4478
+ const total = writeChatMetadataMerged(account, entries);
4479
+ console.error(
4480
+ `${TAG17} [wa-chatmeta] op=persist kind=${kind} account=${account.slice(0, 8)} updates=${entries.length} outcome=${total < 0 ? "fail" : "ok"} entries=${total}`
4481
+ );
4482
+ };
4483
+ sock.ev.on("messaging-history.set", ({ chats, contacts }) => {
4484
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4485
+ const chatEntries = chatEntriesFromChats(chats ?? [], nowIso);
4486
+ const contactEntries = chatEntriesFromContacts(contacts ?? [], nowIso);
4487
+ console.error(
4488
+ `${TAG17} [wa-chatmeta] op=history-set account=${account.slice(0, 8)} chats=${chats?.length ?? 0} contacts=${contacts?.length ?? 0} groups=${chatEntries.filter((e) => e.isGroup).length} named=${chatEntries.length + contactEntries.length}`
4489
+ );
4490
+ void canonicalise([...chatEntries, ...contactEntries]).then((e) => persist("history-set", e, false));
4491
+ });
4492
+ sock.ev.on("chats.upsert", (chats) => {
4493
+ void canonicalise(chatEntriesFromChats(chats ?? [], (/* @__PURE__ */ new Date()).toISOString())).then((e) => persist("chats.upsert", e, true));
4494
+ });
4495
+ sock.ev.on("chats.update", (chats) => {
4496
+ void canonicalise(chatEntriesFromChats(chats ?? [], (/* @__PURE__ */ new Date()).toISOString())).then((e) => persist("chats.update", e, true));
4497
+ });
4498
+ sock.ev.on("contacts.upsert", (contacts) => {
4499
+ void canonicalise(chatEntriesFromContacts(contacts ?? [], (/* @__PURE__ */ new Date()).toISOString())).then((e) => persist("contacts.upsert", e, true));
4500
+ });
4501
+ sock.ev.on("contacts.update", (contacts) => {
4502
+ void canonicalise(chatEntriesFromContacts(contacts ?? [], (/* @__PURE__ */ new Date()).toISOString())).then((e) => persist("contacts.update", e, true));
4503
+ });
4504
+ sock.ev.on("groups.update", (updates) => {
4505
+ persist("groups.update", groupEntriesFromUpdates(updates ?? [], (/* @__PURE__ */ new Date()).toISOString()), true);
4506
+ });
4507
+ const groupFetchAttempted = /* @__PURE__ */ new Set();
4508
+ sock.ev.on("messages.upsert", async (upsert) => {
4509
+ for (const msg of upsert.messages ?? []) {
4510
+ const jid = msg.key?.remoteJid;
4511
+ if (!jid || !jid.endsWith("@g.us")) continue;
4512
+ if (groupFetchAttempted.has(jid)) continue;
4513
+ if (readChatMetadata(account).get(jid)?.source === "group") continue;
4514
+ groupFetchAttempted.add(jid);
4515
+ const startedAt = Date.now();
4516
+ try {
4517
+ const meta = await sock.groupMetadata(jid);
4518
+ console.error(
4519
+ `${TAG17} [wa-chatmeta] op=group-fetch jid=${jid} ms=${Date.now() - startedAt} outcome=ok`
4520
+ );
4521
+ persist(
4522
+ "group-fetch",
4523
+ groupEntriesFromUpdates([{ id: jid, subject: meta.subject }], (/* @__PURE__ */ new Date()).toISOString()),
4524
+ true
4525
+ );
4526
+ } catch (err) {
4527
+ console.error(
4528
+ `${TAG17} [wa-chatmeta] op=group-fetch jid=${jid} ms=${Date.now() - startedAt} outcome=fail reason=${(err instanceof Error ? err.message : String(err)).slice(0, 100)}`
4529
+ );
4530
+ }
4531
+ }
4532
+ });
4533
+ }
4534
+ function listConnections() {
4535
+ return [...connections.values()];
4536
+ }
4125
4537
  function monitorInbound(conn) {
4126
4538
  if (!conn.sock || !onInboundMessage) return;
4127
4539
  const sock = conn.sock;
4128
4540
  const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
4129
- console.error(`${TAG16} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
4541
+ console.error(`${TAG17} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
4130
4542
  conn.debouncer = createInboundDebouncer({
4131
4543
  debounceMs,
4132
4544
  buildKey: (payload) => {
4133
- const conversationKey = payload.isGroup ? payload.groupJid : payload.senderPhone;
4134
- return `${payload.accountId}:${conversationKey ?? payload.senderPhone}:${payload.senderPhone}`;
4545
+ const conversationKey2 = payload.isGroup ? payload.groupJid : payload.senderPhone;
4546
+ return `${payload.accountId}:${conversationKey2 ?? payload.senderPhone}:${payload.senderPhone}`;
4135
4547
  },
4136
4548
  onFlush: (entries) => {
4137
4549
  if (!onInboundMessage) return;
4138
4550
  if (entries.length > 1) {
4139
- console.error(`${TAG16} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
4551
+ console.error(`${TAG17} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
4140
4552
  }
4141
4553
  dispatchInboundPayload(conn, combineInboundBatch(entries));
4142
4554
  },
4143
4555
  onError: (err) => {
4144
- console.error(`${TAG16} debounce flush error account=${conn.accountId}: ${String(err)}`);
4556
+ console.error(`${TAG17} debounce flush error account=${conn.accountId}: ${String(err)}`);
4145
4557
  }
4146
4558
  });
4147
4559
  sock.ev.on("messages.upsert", async (upsert) => {
@@ -4254,7 +4666,7 @@ function monitorInbound(conn) {
4254
4666
  }
4255
4667
  const entries = messageStore.get(storeKey);
4256
4668
  console.error(
4257
- `${TAG16} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) storeAccount=${persistAccountId} socketOwner=${conn.platformAccountId}`
4669
+ `${TAG17} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) storeAccount=${persistAccountId} socketOwner=${conn.platformAccountId}`
4258
4670
  );
4259
4671
  recordActivity({
4260
4672
  accountId: conn.accountId,
@@ -4308,7 +4720,7 @@ function monitorInbound(conn) {
4308
4720
  const wantsServableMedia = mediaServable && !!conn.sock && (!fromMe || !alreadyRecorded);
4309
4721
  if (fromMe && mediaServable && !!conn.sock) {
4310
4722
  console.error(
4311
- `${TAG16} fromMe-media-echo msgKey=${msg.key.id} decision=${alreadyRecorded ? "skip" : "store"} reason=${alreadyRecorded ? "already-recorded" : "no-prior-record"}`
4723
+ `${TAG17} fromMe-media-echo msgKey=${msg.key.id} decision=${alreadyRecorded ? "skip" : "store"} reason=${alreadyRecorded ? "already-recorded" : "no-prior-record"}`
4312
4724
  );
4313
4725
  }
4314
4726
  const mediaSeed = `whatsapp-live:${conn.accountId}:${canonicalRemoteJid}:${msg.key.id}`;
@@ -4391,7 +4803,7 @@ function monitorInbound(conn) {
4391
4803
  receiptDeferredToWorker = true;
4392
4804
  if (!accepted) {
4393
4805
  console.error(
4394
- `${TAG16} append-media overflow account=${conn.platformAccountId} msgKey=${msgKeyId ?? "?"} \u2014 pending cap ${APPEND_MEDIA_MAX_PENDING} reached, text-fallback persist, inline media shed`
4806
+ `${TAG17} append-media overflow account=${conn.platformAccountId} msgKey=${msgKeyId ?? "?"} \u2014 pending cap ${APPEND_MEDIA_MAX_PENDING} reached, text-fallback persist, inline media shed`
4395
4807
  );
4396
4808
  const persisted = await persistWhatsAppMessage({
4397
4809
  ...basePersist,
@@ -4468,21 +4880,21 @@ function monitorInbound(conn) {
4468
4880
  const skippedSenderJid = isGroupJid(remoteJid) ? msg.key.participant ?? remoteJid : remoteJid;
4469
4881
  const skippedSenderPhone = await resolveJidToE164(skippedSenderJid, conn.lidMapping) ?? skippedSenderJid;
4470
4882
  console.error(
4471
- `${TAG16} append-type message ${msg.key.id ?? "?"} dispatch skipped from=${skippedSenderPhone}${msg.pushName ? ` name=${msg.pushName}` : ""} account=${conn.accountId} (persistence not confirmed here)`
4883
+ `${TAG17} append-type message ${msg.key.id ?? "?"} dispatch skipped from=${skippedSenderPhone}${msg.pushName ? ` name=${msg.pushName}` : ""} account=${conn.accountId} (persistence not confirmed here)`
4472
4884
  );
4473
4885
  continue;
4474
4886
  }
4475
4887
  await handleInboundMessage(conn, msg);
4476
4888
  } catch (err) {
4477
- console.error(`${TAG16} inbound handler error account=${conn.accountId}: ${String(err)}`);
4889
+ console.error(`${TAG17} inbound handler error account=${conn.accountId}: ${String(err)}`);
4478
4890
  }
4479
4891
  }
4480
4892
  });
4481
4893
  console.error(`[whatsapp-reaction] op=subscribed account=${conn.platformAccountId}`);
4482
4894
  sock.ev.on("messages.reaction", async (reactions) => {
4483
- for (const entry of reactions) {
4895
+ for (const entry2 of reactions) {
4484
4896
  try {
4485
- const sig = readReactionEntry(entry);
4897
+ const sig = readReactionEntry(entry2);
4486
4898
  if (!sig) continue;
4487
4899
  if (sig.fromMe) continue;
4488
4900
  const reactorTelephone = await resolveJidToE164(sig.reactorJid, conn.lidMapping) ?? sig.reactorJid;
@@ -4530,31 +4942,31 @@ async function handleInboundMessage(conn, msg) {
4530
4942
  const remoteJid = msg.key.remoteJid;
4531
4943
  if (!remoteJid) return;
4532
4944
  if (remoteJid === "status@broadcast") {
4533
- console.error(`${TAG16} drop: status broadcast account=${conn.accountId}`);
4945
+ console.error(`${TAG17} drop: status broadcast account=${conn.accountId}`);
4534
4946
  return;
4535
4947
  }
4536
4948
  if (!msg.message) {
4537
- console.error(`${TAG16} drop: empty message account=${conn.accountId} from=${remoteJid}`);
4949
+ console.error(`${TAG17} drop: empty message account=${conn.accountId} from=${remoteJid}`);
4538
4950
  return;
4539
4951
  }
4540
4952
  const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
4541
4953
  if (isDuplicateInbound(dedupKey)) {
4542
- console.error(`${TAG16} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
4954
+ console.error(`${TAG17} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
4543
4955
  return;
4544
4956
  }
4545
4957
  if (msg.key.fromMe) {
4546
4958
  if (msg.key.id && isAgentSentMessage(msg.key.id)) {
4547
- console.error(`${TAG16} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
4959
+ console.error(`${TAG17} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
4548
4960
  return;
4549
4961
  }
4550
4962
  const extracted2 = extractMessage(msg);
4551
4963
  if (!extracted2.text) {
4552
- console.error(`${TAG16} owner reply skipped \u2014 no text content account=${conn.accountId}`);
4964
+ console.error(`${TAG17} owner reply skipped \u2014 no text content account=${conn.accountId}`);
4553
4965
  return;
4554
4966
  }
4555
4967
  const isGroup2 = isGroupJid(remoteJid);
4556
4968
  const senderPhone2 = conn.selfPhone ?? "owner";
4557
- console.error(`${TAG16} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
4969
+ console.error(`${TAG17} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
4558
4970
  const reply2 = async (text) => {
4559
4971
  const currentSock = conn.sock;
4560
4972
  if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
@@ -4588,7 +5000,7 @@ async function handleInboundMessage(conn, msg) {
4588
5000
  }
4589
5001
  const extracted = extractMessage(msg);
4590
5002
  if (!extracted.text && !extracted.mediaType) {
4591
- console.error(`${TAG16} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
5003
+ console.error(`${TAG17} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
4592
5004
  return;
4593
5005
  }
4594
5006
  const isGroup = isGroupJid(remoteJid);
@@ -4612,7 +5024,7 @@ async function handleInboundMessage(conn, msg) {
4612
5024
  });
4613
5025
  }
4614
5026
  console.error(
4615
- `${TAG16} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
5027
+ `${TAG17} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
4616
5028
  );
4617
5029
  if (!accessResult.allowed && accessResult.reason === "account-manager-unresolved") {
4618
5030
  console.error(
@@ -4649,12 +5061,12 @@ async function handleInboundMessage(conn, msg) {
4649
5061
  maxBytes: maxMb * 1024 * 1024
4650
5062
  });
4651
5063
  if (!mediaResult) {
4652
- console.error(`${TAG16} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
5064
+ console.error(`${TAG17} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
4653
5065
  }
4654
5066
  }
4655
5067
  if (extracted.mediaType === "audio" && mediaResult?.path) {
4656
- const conversationKey = isGroup ? remoteJid : senderPhone;
4657
- const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
5068
+ const conversationKey2 = isGroup ? remoteJid : senderPhone;
5069
+ const debounceKey = `${conn.accountId}:${conversationKey2}:${senderPhone}`;
4658
5070
  let resolvePending;
4659
5071
  const sttPending = new Promise((resolve6) => {
4660
5072
  resolvePending = resolve6;
@@ -4671,7 +5083,7 @@ async function handleInboundMessage(conn, msg) {
4671
5083
  }
4672
5084
  const reply = async (text) => {
4673
5085
  if (isGroupJid(remoteJid)) {
4674
- console.error(`${TAG16} op=group-reply-blocked jid=${remoteJid}`);
5086
+ console.error(`${TAG17} op=group-reply-blocked jid=${remoteJid}`);
4675
5087
  throw new WaReplyError("group reply blocked \u2014 observe-only", false);
4676
5088
  }
4677
5089
  const currentSock = conn.sock;
@@ -4718,15 +5130,15 @@ async function handleInboundMessage(conn, msg) {
4718
5130
  if (accessResult.agentType === "public") {
4719
5131
  const hoursResult = await isBusinessOpen(conn.accountId);
4720
5132
  if (!hoursResult.open) {
4721
- console.error(`${TAG16} [${conn.accountId}] dispatch skipped: business closed`);
5133
+ console.error(`${TAG17} [${conn.accountId}] dispatch skipped: business closed`);
4722
5134
  const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
4723
5135
  if (afterHoursMessage) {
4724
5136
  try {
4725
5137
  await reply(afterHoursMessage);
4726
- console.error(`${TAG16} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
5138
+ console.error(`${TAG17} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
4727
5139
  } catch (err) {
4728
5140
  console.error(
4729
- `${TAG16} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
5141
+ `${TAG17} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
4730
5142
  );
4731
5143
  }
4732
5144
  }
@@ -4775,6 +5187,7 @@ export {
4775
5187
  getAuthAgeMs,
4776
5188
  clearAuth,
4777
5189
  discardAuthDirSync,
5190
+ startVersionDriftCensus,
4778
5191
  drainCredsSaveQueue,
4779
5192
  createWaSocket,
4780
5193
  waitForConnection,
@@ -4782,6 +5195,8 @@ export {
4782
5195
  formatError,
4783
5196
  closeKind,
4784
5197
  classifyDisconnect,
5198
+ LINK_CENSUS_INTERVAL_MS,
5199
+ runLinkCensus,
4785
5200
  normalizeE164,
4786
5201
  toWhatsappJid,
4787
5202
  jidToE164,
@@ -4795,6 +5210,7 @@ export {
4795
5210
  managerPhonesForAccount,
4796
5211
  isAdminPhone,
4797
5212
  shouldDispatchInbound,
5213
+ formatSenderPhone,
4798
5214
  checkAccessRateLimit,
4799
5215
  recordAccessFailedAttempt,
4800
5216
  clearAccessRateLimit,
@@ -4813,10 +5229,6 @@ export {
4813
5229
  sendComposing,
4814
5230
  sendPaused,
4815
5231
  sendMediaMessage,
4816
- resolvePlatformAccountId,
4817
- warnNonUuidAccount,
4818
- isHouseManagedService,
4819
- isCustomerTurnSuppressed,
4820
5232
  DATA_ROOT,
4821
5233
  crossesForeignAccountPartition,
4822
5234
  isWithinOwnAccountPartition,
@@ -4824,6 +5236,13 @@ export {
4824
5236
  resolveClaudeUploadsPath,
4825
5237
  sessionScratchpadRoot,
4826
5238
  resolveScratchpadPath,
5239
+ readChatMetadata,
5240
+ writeChatMetadataMerged,
5241
+ groupEntriesFromUpdates,
5242
+ resolvePlatformAccountId,
5243
+ warnNonUuidAccount,
5244
+ isHouseManagedService,
5245
+ isCustomerTurnSuppressed,
4827
5246
  SUPPORTED_MIME_TYPES,
4828
5247
  MAX_FILE_SIZE_BYTES,
4829
5248
  MAX_FILES_PER_MESSAGE,
@@ -4866,5 +5285,6 @@ export {
4866
5285
  getMessages,
4867
5286
  listConversationJids,
4868
5287
  listStoreAccountKeys,
4869
- shutdown
5288
+ shutdown,
5289
+ listConnections
4870
5290
  };