@rubytech/create-maxy-code 0.1.543 → 0.1.545

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 (280) hide show
  1. package/dist/__tests__/public-instruction-seed.test.js +102 -0
  2. package/dist/index.js +10 -0
  3. package/dist/public-instruction-seed.js +70 -0
  4. package/package.json +1 -1
  5. package/payload/platform/plugins/admin/PLUGIN.md +2 -0
  6. package/payload/platform/plugins/admin/hooks/__tests__/public-tool-surface.test.sh +173 -0
  7. package/payload/platform/plugins/admin/hooks/public-tool-surface.sh +123 -0
  8. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.d.ts.map +1 -1
  9. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.js +13 -1
  10. package/payload/platform/plugins/admin/mcp/dist/tools/agent-write.js.map +1 -1
  11. package/payload/platform/plugins/admin/skills/channel-instruction/SKILL.md +60 -0
  12. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +6 -1
  13. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
  14. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +2 -0
  15. package/payload/platform/plugins/docs/references/internals.md +2 -0
  16. package/payload/platform/plugins/docs/references/platform.md +1 -0
  17. package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
  18. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/availability-snapshot.test.d.ts +2 -0
  19. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/availability-snapshot.test.d.ts.map +1 -0
  20. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/availability-snapshot.test.js +80 -0
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/availability-snapshot.test.js.map +1 -0
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/publish-availability-logging.test.d.ts +2 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/publish-availability-logging.test.d.ts.map +1 -0
  24. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/publish-availability-logging.test.js +32 -0
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/publish-availability-logging.test.js.map +1 -0
  26. package/payload/platform/plugins/scheduling/mcp/dist/lib/availability-snapshot.d.ts +67 -0
  27. package/payload/platform/plugins/scheduling/mcp/dist/lib/availability-snapshot.d.ts.map +1 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/lib/availability-snapshot.js +88 -0
  29. package/payload/platform/plugins/scheduling/mcp/dist/lib/availability-snapshot.js.map +1 -0
  30. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts +2 -1
  31. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts.map +1 -1
  32. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js +59 -54
  33. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js.map +1 -1
  34. package/payload/platform/scripts/__tests__/public-surface-registered.test.sh +102 -0
  35. package/payload/platform/scripts/lib/__tests__/account-settings-public-surface.test.sh +109 -0
  36. package/payload/platform/scripts/lib/account-settings-public-surface.sh +109 -0
  37. package/payload/platform/scripts/lib/provision-account-dir.sh +6 -3
  38. package/payload/platform/scripts/setup-account.sh +14 -0
  39. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
  40. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
  41. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts +22 -0
  42. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
  43. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +23 -1
  44. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
  45. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  46. package/payload/platform/services/claude-session-manager/dist/http-server.js +157 -13
  47. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  48. package/payload/platform/services/claude-session-manager/dist/index.js +86 -40
  49. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  50. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +1 -1
  51. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  52. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +64 -1
  53. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  54. package/payload/platform/services/claude-session-manager/dist/public-surface-audit.d.ts +24 -3
  55. package/payload/platform/services/claude-session-manager/dist/public-surface-audit.d.ts.map +1 -1
  56. package/payload/platform/services/claude-session-manager/dist/public-surface-audit.js +29 -3
  57. package/payload/platform/services/claude-session-manager/dist/public-surface-audit.js.map +1 -1
  58. package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts +27 -0
  59. package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts.map +1 -1
  60. package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js +32 -0
  61. package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js.map +1 -1
  62. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +15 -0
  63. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
  64. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +8 -0
  65. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
  66. package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.d.ts +18 -9
  67. package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.d.ts.map +1 -1
  68. package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.js +18 -9
  69. package/payload/platform/services/claude-session-manager/dist/stuck-turn-recover.js.map +1 -1
  70. package/payload/platform/services/claude-session-manager/dist/stuck-turn-respawn.d.ts +60 -0
  71. package/payload/platform/services/claude-session-manager/dist/stuck-turn-respawn.d.ts.map +1 -0
  72. package/payload/platform/services/claude-session-manager/dist/stuck-turn-respawn.js +93 -0
  73. package/payload/platform/services/claude-session-manager/dist/stuck-turn-respawn.js.map +1 -0
  74. package/payload/platform/services/claude-session-manager/dist/stuck-turn.d.ts +12 -1
  75. package/payload/platform/services/claude-session-manager/dist/stuck-turn.d.ts.map +1 -1
  76. package/payload/platform/services/claude-session-manager/dist/stuck-turn.js +41 -13
  77. package/payload/platform/services/claude-session-manager/dist/stuck-turn.js.map +1 -1
  78. package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
  79. package/payload/platform/services/telegram-channel/dist/instructions.js +17 -16
  80. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
  81. package/payload/platform/services/telegram-channel/dist/notification.d.ts +10 -4
  82. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  83. package/payload/platform/services/telegram-channel/dist/notification.js +19 -12
  84. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  85. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  86. package/payload/platform/services/telegram-channel/dist/server.js +3 -0
  87. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  88. package/payload/platform/services/webchat-channel/dist/instructions.js +21 -21
  89. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  90. package/payload/platform/services/webchat-channel/dist/notification.d.ts +9 -2
  91. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  92. package/payload/platform/services/webchat-channel/dist/notification.js +20 -10
  93. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  94. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  95. package/payload/platform/services/webchat-channel/dist/server.js +5 -0
  96. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  97. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +43 -23
  98. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  99. package/payload/platform/services/whatsapp-channel/dist/notification.js +86 -35
  100. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  101. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  102. package/payload/platform/services/whatsapp-channel/dist/server.js +15 -6
  103. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  104. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +17 -6
  105. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  106. package/payload/platform/services/whatsapp-channel/dist/targets.js +13 -2
  107. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  108. package/payload/platform/templates/agents/admin/INSTRUCTION.md +0 -15
  109. package/payload/server/{chunk-MGW536C5.js → chunk-CVUSUNXM.js} +1 -1
  110. package/payload/server/{chunk-BIHII3CT.js → chunk-DU7GPDJJ.js} +1 -0
  111. package/payload/server/{chunk-RSNVIN4G.js → chunk-X2RRMHVV.js} +1 -1
  112. package/payload/server/{manager-GJHKO7WW.js → manager-TI2YJQVZ.js} +2 -2
  113. package/payload/server/maxy-edge.js +2 -2
  114. package/payload/server/public/activity.html +5 -5
  115. package/payload/server/public/agents.html +4 -4
  116. package/payload/server/public/assets/{AdminLoginScreens-C1iXeR-9.js → AdminLoginScreens-BGKtDqAk.js} +1 -1
  117. package/payload/server/public/assets/AdminLoginScreens-BGKtDqAk.js.br +0 -0
  118. package/payload/server/public/assets/AdminLoginScreens-BGKtDqAk.js.gz +0 -0
  119. package/payload/server/public/assets/{AdminShell-BeIGNC1Q.js → AdminShell-DnfgsAQl.js} +1 -1
  120. package/payload/server/public/assets/AdminShell-DnfgsAQl.js.br +0 -0
  121. package/payload/server/public/assets/AdminShell-DnfgsAQl.js.gz +0 -0
  122. package/payload/server/public/assets/{activity-AaFvfwrZ.js → activity-ByJSut0Q.js} +1 -1
  123. package/payload/server/public/assets/activity-ByJSut0Q.js.br +0 -0
  124. package/payload/server/public/assets/activity-ByJSut0Q.js.gz +0 -0
  125. package/payload/server/public/assets/{admin-Bz34CZqL.js → admin-CkeJO6kA.js} +1 -1
  126. package/payload/server/public/assets/admin-CkeJO6kA.js.br +0 -0
  127. package/payload/server/public/assets/admin-CkeJO6kA.js.gz +0 -0
  128. package/payload/server/public/assets/{agents-N0z7qg8a.js → agents-D8phtRX5.js} +1 -1
  129. package/payload/server/public/assets/agents-D8phtRX5.js.br +0 -0
  130. package/payload/server/public/assets/agents-D8phtRX5.js.gz +0 -0
  131. package/payload/server/public/assets/{browser-_k-v0iky.js → browser-DQSTjdom.js} +1 -1
  132. package/payload/server/public/assets/browser-DQSTjdom.js.br +0 -0
  133. package/payload/server/public/assets/browser-DQSTjdom.js.gz +0 -0
  134. package/payload/server/public/assets/{calendar-D_DvVRJS.js → calendar-DFJu1XjP.js} +1 -1
  135. package/payload/server/public/assets/calendar-DFJu1XjP.js.br +0 -0
  136. package/payload/server/public/assets/calendar-DFJu1XjP.js.gz +0 -0
  137. package/payload/server/public/assets/chat-DUqtIsOt.js +1 -0
  138. package/payload/server/public/assets/chat-DUqtIsOt.js.br +0 -0
  139. package/payload/server/public/assets/chat-DUqtIsOt.js.gz +0 -0
  140. package/payload/server/public/assets/chevron-left-DFK18xVX.js +1 -0
  141. package/payload/server/public/assets/chevron-left-DFK18xVX.js.br +0 -0
  142. package/payload/server/public/assets/chevron-right-DJ4oaHoN.js +1 -0
  143. package/payload/server/public/assets/chevron-right-DJ4oaHoN.js.br +0 -0
  144. package/payload/server/public/assets/clock-wHIan2DW.js +1 -0
  145. package/payload/server/public/assets/clock-wHIan2DW.js.br +0 -0
  146. package/payload/server/public/assets/{copy-BDoxl3Oi.js → copy-lFFhXRGk.js} +1 -1
  147. package/payload/server/public/assets/copy-lFFhXRGk.js.br +0 -0
  148. package/payload/server/public/assets/copy-lFFhXRGk.js.gz +0 -0
  149. package/payload/server/public/assets/data-Cxv97ARy.js +1 -0
  150. package/payload/server/public/assets/data-Cxv97ARy.js.br +0 -0
  151. package/payload/server/public/assets/data-Cxv97ARy.js.gz +0 -0
  152. package/payload/server/public/assets/{file-text-DRGn4gjH.js → file-text-D2Y-bkNy.js} +1 -1
  153. package/payload/server/public/assets/file-text-D2Y-bkNy.js.br +0 -0
  154. package/payload/server/public/assets/file-text-D2Y-bkNy.js.gz +0 -0
  155. package/payload/server/public/assets/{graph-D7ECsLPs.js → graph-C-3yN6JD.js} +1 -1
  156. package/payload/server/public/assets/graph-C-3yN6JD.js.br +0 -0
  157. package/payload/server/public/assets/graph-C-3yN6JD.js.gz +0 -0
  158. package/payload/server/public/assets/{graph-labels-ClzmLEoK.js → graph-labels-DiaPLP27.js} +1 -1
  159. package/payload/server/public/assets/graph-labels-DiaPLP27.js.br +0 -0
  160. package/payload/server/public/assets/graph-labels-DiaPLP27.js.gz +0 -0
  161. package/payload/server/public/assets/{operator-jSdha7TP.js → operator-KuGPvSa9.js} +1 -1
  162. package/payload/server/public/assets/operator-KuGPvSa9.js.br +0 -0
  163. package/payload/server/public/assets/operator-KuGPvSa9.js.gz +0 -0
  164. package/payload/server/public/assets/{page-Cq4SkWbr.js → page-BhGeFXWA.js} +1 -1
  165. package/payload/server/public/assets/page-BhGeFXWA.js.br +0 -0
  166. package/payload/server/public/assets/page-BhGeFXWA.js.gz +0 -0
  167. package/payload/server/public/assets/{page-DrwRXi3K.js → page-EnbVC-yM.js} +1 -1
  168. package/payload/server/public/assets/page-EnbVC-yM.js.br +0 -0
  169. package/payload/server/public/assets/page-EnbVC-yM.js.gz +0 -0
  170. package/payload/server/public/assets/play-jZFZAG-t.js +1 -0
  171. package/payload/server/public/assets/play-jZFZAG-t.js.br +0 -0
  172. package/payload/server/public/assets/play-jZFZAG-t.js.gz +0 -0
  173. package/payload/server/public/assets/{public-D7MfkWvM.js → public-CdSgMNC7.js} +1 -1
  174. package/payload/server/public/assets/public-CdSgMNC7.js.br +0 -0
  175. package/payload/server/public/assets/public-CdSgMNC7.js.gz +0 -0
  176. package/payload/server/public/assets/{rotate-ccw-CaWP-njL.js → rotate-ccw-rTRuTqei.js} +1 -1
  177. package/payload/server/public/assets/rotate-ccw-rTRuTqei.js.br +0 -0
  178. package/payload/server/public/assets/rotate-ccw-rTRuTqei.js.gz +0 -0
  179. package/payload/server/public/assets/{routines-XTk0vJUD.js → routines-DNRxNRSQ.js} +1 -1
  180. package/payload/server/public/assets/routines-DNRxNRSQ.js.br +0 -0
  181. package/payload/server/public/assets/routines-DNRxNRSQ.js.gz +0 -0
  182. package/payload/server/public/assets/{skills-sjTaAr0v.js → skills-DFWtZNkW.js} +1 -1
  183. package/payload/server/public/assets/skills-DFWtZNkW.js.br +0 -0
  184. package/payload/server/public/assets/skills-DFWtZNkW.js.gz +0 -0
  185. package/payload/server/public/assets/{tasks-D0-qwWZR.js → tasks-B94AuKSy.js} +1 -1
  186. package/payload/server/public/assets/tasks-B94AuKSy.js.br +0 -0
  187. package/payload/server/public/assets/tasks-B94AuKSy.js.gz +0 -0
  188. package/payload/server/public/assets/{triangle-alert-BCndBPvE.js → triangle-alert-DtWUg6jR.js} +1 -1
  189. package/payload/server/public/assets/triangle-alert-DtWUg6jR.js.br +0 -0
  190. package/payload/server/public/assets/triangle-alert-DtWUg6jR.js.gz +0 -0
  191. package/payload/server/public/assets/{useCopyFeedback-BFMmBvVw.js → useCopyFeedback-CjKYO-Qf.js} +1 -1
  192. package/payload/server/public/assets/useCopyFeedback-CjKYO-Qf.js.br +0 -0
  193. package/payload/server/public/assets/useCopyFeedback-CjKYO-Qf.js.gz +0 -0
  194. package/payload/server/public/assets/{useMediaQuery-CXdUnGSe.css → useMediaQuery-DlOWP56H.css} +1 -1
  195. package/payload/server/public/assets/useMediaQuery-DlOWP56H.css.br +0 -0
  196. package/payload/server/public/assets/{useMediaQuery-CXdUnGSe.css.gz → useMediaQuery-DlOWP56H.css.gz} +0 -0
  197. package/payload/server/public/assets/{useVoiceRecorder-DmW6CW7B.js → useVoiceRecorder-BKFAYuzz.js} +1 -1
  198. package/payload/server/public/assets/useVoiceRecorder-BKFAYuzz.js.br +0 -0
  199. package/payload/server/public/assets/useVoiceRecorder-BKFAYuzz.js.gz +0 -0
  200. package/payload/server/public/assets/{wrench-BBl51xDU.js → wrench-Ct4kf-uv.js} +1 -1
  201. package/payload/server/public/assets/wrench-Ct4kf-uv.js.br +0 -0
  202. package/payload/server/public/assets/wrench-Ct4kf-uv.js.gz +0 -0
  203. package/payload/server/public/browser.html +4 -4
  204. package/payload/server/public/calendar.html +8 -8
  205. package/payload/server/public/chat.html +13 -13
  206. package/payload/server/public/data.html +11 -11
  207. package/payload/server/public/graph.html +9 -9
  208. package/payload/server/public/index.html +14 -14
  209. package/payload/server/public/operator.html +14 -14
  210. package/payload/server/public/public.html +13 -13
  211. package/payload/server/public/routines.html +6 -6
  212. package/payload/server/public/skills.html +5 -5
  213. package/payload/server/public/tasks.html +6 -6
  214. package/payload/server/server.js +48 -21
  215. package/payload/server/public/assets/AdminLoginScreens-C1iXeR-9.js.br +0 -0
  216. package/payload/server/public/assets/AdminLoginScreens-C1iXeR-9.js.gz +0 -0
  217. package/payload/server/public/assets/AdminShell-BeIGNC1Q.js.br +0 -0
  218. package/payload/server/public/assets/AdminShell-BeIGNC1Q.js.gz +0 -0
  219. package/payload/server/public/assets/activity-AaFvfwrZ.js.br +0 -0
  220. package/payload/server/public/assets/activity-AaFvfwrZ.js.gz +0 -0
  221. package/payload/server/public/assets/admin-Bz34CZqL.js.br +0 -0
  222. package/payload/server/public/assets/admin-Bz34CZqL.js.gz +0 -0
  223. package/payload/server/public/assets/agents-N0z7qg8a.js.br +0 -0
  224. package/payload/server/public/assets/agents-N0z7qg8a.js.gz +0 -0
  225. package/payload/server/public/assets/browser-_k-v0iky.js.br +0 -0
  226. package/payload/server/public/assets/browser-_k-v0iky.js.gz +0 -0
  227. package/payload/server/public/assets/calendar-D_DvVRJS.js.br +0 -0
  228. package/payload/server/public/assets/calendar-D_DvVRJS.js.gz +0 -0
  229. package/payload/server/public/assets/chat-BbAsvXL_.js +0 -1
  230. package/payload/server/public/assets/chat-BbAsvXL_.js.br +0 -1
  231. package/payload/server/public/assets/chat-BbAsvXL_.js.gz +0 -0
  232. package/payload/server/public/assets/chevron-left-trZmu0ZU.js +0 -1
  233. package/payload/server/public/assets/chevron-left-trZmu0ZU.js.br +0 -0
  234. package/payload/server/public/assets/chevron-right-BiGJuNSX.js +0 -1
  235. package/payload/server/public/assets/chevron-right-BiGJuNSX.js.br +0 -0
  236. package/payload/server/public/assets/clock-DAeRdj4p.js +0 -1
  237. package/payload/server/public/assets/clock-DAeRdj4p.js.br +0 -0
  238. package/payload/server/public/assets/clock-DAeRdj4p.js.gz +0 -0
  239. package/payload/server/public/assets/copy-BDoxl3Oi.js.br +0 -0
  240. package/payload/server/public/assets/copy-BDoxl3Oi.js.gz +0 -0
  241. package/payload/server/public/assets/data-DB6T1I-w.js +0 -1
  242. package/payload/server/public/assets/data-DB6T1I-w.js.br +0 -1
  243. package/payload/server/public/assets/data-DB6T1I-w.js.gz +0 -0
  244. package/payload/server/public/assets/file-text-DRGn4gjH.js.br +0 -0
  245. package/payload/server/public/assets/file-text-DRGn4gjH.js.gz +0 -0
  246. package/payload/server/public/assets/graph-D7ECsLPs.js.br +0 -0
  247. package/payload/server/public/assets/graph-D7ECsLPs.js.gz +0 -0
  248. package/payload/server/public/assets/graph-labels-ClzmLEoK.js.br +0 -0
  249. package/payload/server/public/assets/graph-labels-ClzmLEoK.js.gz +0 -0
  250. package/payload/server/public/assets/operator-jSdha7TP.js.br +0 -0
  251. package/payload/server/public/assets/operator-jSdha7TP.js.gz +0 -0
  252. package/payload/server/public/assets/page-Cq4SkWbr.js.br +0 -0
  253. package/payload/server/public/assets/page-Cq4SkWbr.js.gz +0 -0
  254. package/payload/server/public/assets/page-DrwRXi3K.js.br +0 -0
  255. package/payload/server/public/assets/page-DrwRXi3K.js.gz +0 -0
  256. package/payload/server/public/assets/play-CWyPEXiB.js +0 -1
  257. package/payload/server/public/assets/play-CWyPEXiB.js.br +0 -0
  258. package/payload/server/public/assets/play-CWyPEXiB.js.gz +0 -0
  259. package/payload/server/public/assets/public-D7MfkWvM.js.br +0 -0
  260. package/payload/server/public/assets/public-D7MfkWvM.js.gz +0 -0
  261. package/payload/server/public/assets/rotate-ccw-CaWP-njL.js.br +0 -0
  262. package/payload/server/public/assets/rotate-ccw-CaWP-njL.js.gz +0 -0
  263. package/payload/server/public/assets/routines-XTk0vJUD.js.br +0 -0
  264. package/payload/server/public/assets/routines-XTk0vJUD.js.gz +0 -0
  265. package/payload/server/public/assets/skills-sjTaAr0v.js.br +0 -0
  266. package/payload/server/public/assets/skills-sjTaAr0v.js.gz +0 -0
  267. package/payload/server/public/assets/tasks-D0-qwWZR.js.br +0 -0
  268. package/payload/server/public/assets/tasks-D0-qwWZR.js.gz +0 -0
  269. package/payload/server/public/assets/triangle-alert-BCndBPvE.js.br +0 -0
  270. package/payload/server/public/assets/triangle-alert-BCndBPvE.js.gz +0 -0
  271. package/payload/server/public/assets/useCopyFeedback-BFMmBvVw.js.br +0 -0
  272. package/payload/server/public/assets/useCopyFeedback-BFMmBvVw.js.gz +0 -0
  273. package/payload/server/public/assets/useMediaQuery-CXdUnGSe.css.br +0 -0
  274. package/payload/server/public/assets/useVoiceRecorder-DmW6CW7B.js.br +0 -0
  275. package/payload/server/public/assets/useVoiceRecorder-DmW6CW7B.js.gz +0 -0
  276. package/payload/server/public/assets/wrench-BBl51xDU.js.br +0 -0
  277. package/payload/server/public/assets/wrench-BBl51xDU.js.gz +0 -0
  278. /package/payload/server/public/assets/{useMediaQuery-q9BSy1Ld.js → useMediaQuery-BYIHo-xL.js} +0 -0
  279. /package/payload/server/public/assets/{useMediaQuery-q9BSy1Ld.js.br → useMediaQuery-BYIHo-xL.js.br} +0 -0
  280. /package/payload/server/public/assets/{useMediaQuery-q9BSy1Ld.js.gz → useMediaQuery-BYIHo-xL.js.gz} +0 -0
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: channel-instruction
3
+ description: "Set up or change what this account's agent does when a message arrives from someone who is not an account manager. Triggers when the owner says 'set up what my agent does with incoming messages', 'change how you handle WhatsApp messages from customers', 'write my channel instruction', 'my agent replied to a customer and it shouldn't have', or asks why an incoming message got no answer."
4
+ ---
5
+
6
+ # Channel instruction
7
+
8
+ Invoked by the admin agent directly.
9
+
10
+ When someone who is not an account manager sends a WhatsApp message to this account's line, the platform wakes each manager's own session with a turn about it. That turn is handed a `## Context` block holding the message and an `## Instruction` block saying what to do with it. This skill is about the file that supplies the second block.
11
+
12
+ ## The file
13
+
14
+ `$ACCOUNT_DIR/agents/admin/INSTRUCTION.md`
15
+
16
+ Its whole body becomes the `## Instruction` block on that turn, verbatim. The platform wraps it in nothing and adds no wording of its own.
17
+
18
+ The install creates the file empty. What goes in it is the account owner's, not the platform's.
19
+
20
+ ## Which turns it governs, and which it does not
21
+
22
+ It governs one kind of turn: a WhatsApp message from someone who is not an account manager. Nothing else.
23
+
24
+ **A message the owner sends is unaffected by this file.** Whether they type it in the admin webchat, send it from their own WhatsApp handset, or it arrives from the scheduler, the platform supplies its own directive for that turn and never reads this file. An instruction written here for handling a customer will not change how the agent answers the owner.
25
+
26
+ **A public agent does not read this file either.** An account's public agent reads `$ACCOUNT_DIR/agents/<its-slug>/INSTRUCTION.md`, its own. Writing a rule here in the hope of steering the public agent does nothing.
27
+
28
+ If the owner wants to change how the agent behaves toward them personally, this is the wrong file. Identity and manner live in `IDENTITY.md` and `SOUL.md` in the same directory, and they apply to every session.
29
+
30
+ ## An empty file drops the message
31
+
32
+ A file that is missing, unreadable, or holds only whitespace resolves to absent, and an absent instruction refuses the turn: no session wakes, and nothing happens about the message. The sender gets silence and the owner is never told.
33
+
34
+ Nothing warns the owner in advance. The only trace is two lines in `server.log`, written at the moment the message was dropped:
35
+
36
+ - `op=instruction-source accountId=<id> turn=relay source=absent bytes=0`
37
+ - `op=inbound … delivery=refused` for the same message
38
+
39
+ So on an account whose WhatsApp line is connected and whose file is empty, writing this file is the fix for "a customer wrote in and nothing happened". If the owner reports that, check this file before anything else. Their own chat will be working normally throughout, which is what makes this hard to spot.
40
+
41
+ ## Writing it
42
+
43
+ Read the file first and tell the owner what is in it. Then find out what they want and write it.
44
+
45
+ What belongs in it is what should happen when a stranger writes in: who the agent may answer on its own and who it must not, what has to be put to a person, what it must never say, and what it must always do before the turn ends.
46
+
47
+ Two rules on how you write it:
48
+
49
+ - Write only what the owner has told you. A rule they did not ask for is someone else's wording in their file, which is exactly what this file exists to keep out.
50
+ - Never carry rules over from another account or from another owner's file.
51
+
52
+ An edit takes effect on the next incoming message. No restart, no upgrade, no publish. So a change can be checked by having someone message the account's WhatsApp line and reading what the agent does.
53
+
54
+ ## Checking which body a turn actually took
55
+
56
+ Every WhatsApp turn writes one line to `server.log` naming the decision:
57
+
58
+ `op=instruction-source accountId=<id> agentDir=<dir> turn=relay|direct|public source=account|platform|absent bytes=<n>`
59
+
60
+ `turn=relay` with `source=account` means the turn took this file. `turn=direct` with `source=platform` means the owner sent it themselves and this file was not read. If the owner believes a rule they wrote is being applied to their own messages, that line settles it.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:16c402596ec41a4862a881217aada22c79ea15e59da8e41c624a0ab494952fc9
4
+ content-hash: sha256:4566431caa948d080401758fc742c6b3f6340a0a1d17129ff51c9887836aaf87
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -250,6 +250,7 @@ Inside the scope, `sh -c 'trap "" HUP; exec "$@"' sh <claudeBin> <args...>` keep
250
250
  | Step | Line shape |
251
251
  |------|-----------|
252
252
  | 1 | `[rc-spawn] op=request unitToken=<t> sessionId=<8|new> name=<…|none> mode=<resume|fresh> jsonl=<path|none>` |
253
+ | 1b | `[rc-spawn] op=role-resolution sessionId=<8> source=<body\|sidecar\|unresolved> outcome=<ok\|missing\|parse-failed>` — channel-bound spawns only. `source` is where the session's role came from; `outcome` is the `readSidecar` verdict behind it, which separates a deleted classification sidecar from a corrupted one. `source=unresolved` means neither the body nor the sidecar produced a role, so the spawn took the public lockdown rather than running unrestricted. The `tool-allowlist` line cannot make that distinction: it renders `role=none` for an unresolved role and for a legitimately roleless spawn alike. |
253
254
  | 2 | `[rc-spawn] op=argv unitToken=<t> cwd=<dir> argv=<json>` (inner claude argv; the `systemd-run --scope` wrap is composed by the spawnPty adapter) |
254
255
  | 3 | `[rc-spawn] op=pty-spawned unitToken=<t> pid=<pid> openFds=<n>` (fd baseline) |
255
256
  | 4 | `[rc-spawn] op=child-output unitToken=<t> pid=<pid> head=<json>` (first ≤1 KB or 500 ms idle — claude's own words) |
@@ -1709,6 +1710,8 @@ Every turn carries a `source`. Three values exist:
1709
1710
  | `schedule` | The scheduler wrote this turn. The named destination did not send it. |
1710
1711
  | `public-relay` | The platform wrote this turn because a public visitor messaged the account. The named sender is a manager being told, not someone who wrote to you. |
1711
1712
 
1713
+ **A `public-relay` turn is the one turn whose `## Instruction` block is the account's own.** Its body is `agents/admin/INSTRUCTION.md`, written by the account owner, and it is the only instruction that turn carries: no standing-rules block rides with it. Every other turn on this line, including one the owner sent from their own handset and one the scheduler wrote, takes the platform's own directive and never reads that file. If the file is empty the relay turn is refused outright, so the message is dropped and nobody is told. A public visitor's own turn reads its agent's file at `agents/<slug>/INSTRUCTION.md`, never the admin's.
1714
+
1712
1715
  A `public-relay` turn also says so in its own first two lines, and states which handset its reply tool reaches. That handset is the manager's, never the visitor's: the turn runs in the manager's own admin session, so a reply written to the visitor is delivered to the manager. If a turn claims a manager wrote something they plainly would not have written, check the source before answering them.
1713
1716
 
1714
1717
  **On a `public-relay` turn the reply tool is the only way to reach anyone.** On an ordinary turn the platform forwards your closing text to the sender when you called no reply tool. On a relay-woken turn it does not, because the relay carries its own reply path. So whatever you decide the manager should receive has to go through the reply tool; text you merely write at the end of the turn reaches nobody, and a decision not to reply is silence rather than a message.
@@ -5571,6 +5574,8 @@ The final step in the retrieval pipeline is injecting retrieved content into the
5571
5574
 
5572
5575
  The manager exposes three named spawn routes: `/rc-spawn` (a live `claude --remote-control` PTY — the operator sidebar, the channel admin, and the one-shot admin jobs), `/public-spawn` (the renamed `/spawn` — `spawnClaudeSession`, the zero-tool public surface), and the `rc-daemon` it drives. No route named `/spawn` remains; a manager boot line `[spawn-routes] live=[rc-spawn,public-spawn]` asserts this.
5573
5576
 
5577
+ **A public spawn may not name a specialist.** The zero-tool surface is built from four levers, and two of them — the `--allowed-tools` push and the resolved permission mode — branch on whether the spawn is a specialist rather than on whether it is public, so a specialist named on a public spawn would skip the allowlist entirely and resolve the caller's mode instead of the fail-closed `dontAsk`. Only the per-spawn `permissions.deny` and `--strict-mcp-config` bind on the public role alone. Neither production issuer sends the pairing: the channel bridge is the only caller whose role can be public and it carries no specialist field, while the cookie-auth session wrapper is admin-only. So the pairing is refused rather than reshaped — `/public-spawn` answers `400 specialist-not-permitted-on-public`, and `spawnClaudeSession` refuses it again with `reason=public-specialist-refused` at the single chokepoint every spawn route passes through. Refusing rather than narrowing the levers is deliberate: a narrowed lever would leave a specialist running silently on a one-tool surface. Every spawn that reaches `spawnClaudeSession` — which is every `/public-spawn`, that route being its only caller — logs the three deciding inputs together as `[spawn-lockdown] role=<role> specialist=<name|none> permissionMode=<mode>`. A refusal at either layer logs `op=public-specialist-refused` instead, told apart by `site=`.
5578
+
5574
5579
  The channel PTY-bridge (`ensureEntry`) routes each inbound by role: an **admin** WhatsApp/Telegram inbound spawns on `/rc-spawn` (keyed by a deterministic per-sender sessionId so the thread resumes across restarts) and drives every turn via `/<id>/input`; a **non-admin** inbound spawns on `/public-spawn`. Each dispatch logs `[<channel>-adaptor] route role=<role> target=<rc-spawn|public-spawn> senderId=…`. LinkedIn ingest and the public session-end review also run on `/rc-spawn`, carrying their prompt as `initialMessage` with `closeAfterTurn` so the PTY stops after one assistant turn.
5575
5580
 
5576
5581
  ### Public agent paths
@@ -9,6 +9,18 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-08-02 (0.1.545)
13
+
14
+ - The assistant is now instructed differently depending on whether you messaged it directly or the turn was relayed from someone outside your team, so your own standing rules no longer ride along on a relayed turn.
15
+ - Publishing your booking availability now goes through Cloudflare's own interface rather than a command-line tool, and a retry that can no longer finish in time is refused instead of hanging.
16
+
17
+ ## 2026-08-02 (0.1.544)
18
+
19
+ - Every channel, including Telegram, now takes its turn instruction from the account's own file rather than text fixed in the code, and a build check stops any of the three services hardcoding it again.
20
+ - Each public agent now has its own instruction file, created on install.
21
+ - A session whose role cannot be determined is now refused rather than allowed through, and a specialist can no longer be requested on a session serving someone outside your team.
22
+ - A frozen session serving someone outside your team is now restarted through the same restricted route it began on.
23
+
12
24
  ## 2026-08-01 (0.1.543)
13
25
 
14
26
  - There is now a shared folder every account can read and only the main account can write to, and an upload route that could reach out of an account into it has been closed.
@@ -28,6 +28,8 @@ Every turn carries a `source`. Three values exist:
28
28
  | `schedule` | The scheduler wrote this turn. The named destination did not send it. |
29
29
  | `public-relay` | The platform wrote this turn because a public visitor messaged the account. The named sender is a manager being told, not someone who wrote to you. |
30
30
 
31
+ **A `public-relay` turn is the one turn whose `## Instruction` block is the account's own.** Its body is `agents/admin/INSTRUCTION.md`, written by the account owner, and it is the only instruction that turn carries: no standing-rules block rides with it. Every other turn on this line, including one the owner sent from their own handset and one the scheduler wrote, takes the platform's own directive and never reads that file. If the file is empty the relay turn is refused outright, so the message is dropped and nobody is told. A public visitor's own turn reads its agent's file at `agents/<slug>/INSTRUCTION.md`, never the admin's.
32
+
31
33
  A `public-relay` turn also says so in its own first two lines, and states which handset its reply tool reaches. That handset is the manager's, never the visitor's: the turn runs in the manager's own admin session, so a reply written to the visitor is delivered to the manager. If a turn claims a manager wrote something they plainly would not have written, check the source before answering them.
32
34
 
33
35
  **On a `public-relay` turn the reply tool is the only way to reach anyone.** On an ordinary turn the platform forwards your closing text to the sender when you called no reply tool. On a relay-woken turn it does not, because the relay carries its own reply path. So whatever you decide the manager should receive has to go through the reply tool; text you merely write at the end of the turn reaches nobody, and a decision not to reply is silence rather than a message.
@@ -357,6 +357,8 @@ The final step in the retrieval pipeline is injecting retrieved content into the
357
357
 
358
358
  The manager exposes three named spawn routes: `/rc-spawn` (a live `claude --remote-control` PTY — the operator sidebar, the channel admin, and the one-shot admin jobs), `/public-spawn` (the renamed `/spawn` — `spawnClaudeSession`, the zero-tool public surface), and the `rc-daemon` it drives. No route named `/spawn` remains; a manager boot line `[spawn-routes] live=[rc-spawn,public-spawn]` asserts this.
359
359
 
360
+ **A public spawn may not name a specialist.** The zero-tool surface is built from four levers, and two of them — the `--allowed-tools` push and the resolved permission mode — branch on whether the spawn is a specialist rather than on whether it is public, so a specialist named on a public spawn would skip the allowlist entirely and resolve the caller's mode instead of the fail-closed `dontAsk`. Only the per-spawn `permissions.deny` and `--strict-mcp-config` bind on the public role alone. Neither production issuer sends the pairing: the channel bridge is the only caller whose role can be public and it carries no specialist field, while the cookie-auth session wrapper is admin-only. So the pairing is refused rather than reshaped — `/public-spawn` answers `400 specialist-not-permitted-on-public`, and `spawnClaudeSession` refuses it again with `reason=public-specialist-refused` at the single chokepoint every spawn route passes through. Refusing rather than narrowing the levers is deliberate: a narrowed lever would leave a specialist running silently on a one-tool surface. Every spawn that reaches `spawnClaudeSession` — which is every `/public-spawn`, that route being its only caller — logs the three deciding inputs together as `[spawn-lockdown] role=<role> specialist=<name|none> permissionMode=<mode>`. A refusal at either layer logs `op=public-specialist-refused` instead, told apart by `site=`.
361
+
360
362
  The channel PTY-bridge (`ensureEntry`) routes each inbound by role: an **admin** WhatsApp/Telegram inbound spawns on `/rc-spawn` (keyed by a deterministic per-sender sessionId so the thread resumes across restarts) and drives every turn via `/<id>/input`; a **non-admin** inbound spawns on `/public-spawn`. Each dispatch logs `[<channel>-adaptor] route role=<role> target=<rc-spawn|public-spawn> senderId=…`. LinkedIn ingest and the public session-end review also run on `/rc-spawn`, carrying their prompt as `initialMessage` with `closeAfterTurn` so the PTY stops after one assistant turn.
361
363
 
362
364
  ### Public agent paths
@@ -123,6 +123,7 @@ Inside the scope, `sh -c 'trap "" HUP; exec "$@"' sh <claudeBin> <args...>` keep
123
123
  | Step | Line shape |
124
124
  |------|-----------|
125
125
  | 1 | `[rc-spawn] op=request unitToken=<t> sessionId=<8|new> name=<…|none> mode=<resume|fresh> jsonl=<path|none>` |
126
+ | 1b | `[rc-spawn] op=role-resolution sessionId=<8> source=<body\|sidecar\|unresolved> outcome=<ok\|missing\|parse-failed>` — channel-bound spawns only. `source` is where the session's role came from; `outcome` is the `readSidecar` verdict behind it, which separates a deleted classification sidecar from a corrupted one. `source=unresolved` means neither the body nor the sidecar produced a role, so the spawn took the public lockdown rather than running unrestricted. The `tool-allowlist` line cannot make that distinction: it renders `role=none` for an unresolved role and for a legitimately roleless spawn alike. |
126
127
  | 2 | `[rc-spawn] op=argv unitToken=<t> cwd=<dir> argv=<json>` (inner claude argv; the `systemd-run --scope` wrap is composed by the spawnPty adapter) |
127
128
  | 3 | `[rc-spawn] op=pty-spawned unitToken=<t> pid=<pid> openFds=<n>` (fd baseline) |
128
129
  | 4 | `[rc-spawn] op=child-output unitToken=<t> pid=<pid> head=<json>` (first ≤1 KB or 500 ms idle — claude's own words) |
@@ -274,7 +274,7 @@ Dispatch results are recorded on the Event node (`lastDispatchResult`, `lastDisp
274
274
 
275
275
  It is **armed by the always-running `platform/ui` server on an interval**, because the Pi has no other periodic surface (the same gap `check-due-events` has). A PID lock at `<configDir>/reconcile-bookings.lock` prevents overlapping runs. It registers **no agent tool** — there is nothing for the agent to call — so the tool-surface registries are untouched.
276
276
 
277
- Env: `PLATFORM_ROOT`, `NEO4J_URI` (and `NEO4J_PASSWORD` or `config/.neo4j-password`). Per account it reads `data/accounts/<id>/calendar-availability.json` (for `bookingDbName`). The Cloudflare credential is read house-only from `config/cloudflare-house.env`; that credential is a minter, so the calendar mints a `calendar-d1` (D1-only) token from it via `cf-token.sh` and shells `wrangler` for the D1 reads/writes on that scoped token, never the minter directly.
277
+ Env: `PLATFORM_ROOT`, `NEO4J_URI` (and `NEO4J_PASSWORD` or `config/.neo4j-password`). Per account it reads `data/accounts/<id>/calendar-availability.json` (for `bookingDbName`). The Cloudflare credential is read house-only from `config/cloudflare-house.env`; that credential is a minter, so the calendar mints a `calendar-d1` (D1-only) token from it via `cf-token.sh` and authenticates the D1 reads/writes on that scoped token, never the minter directly. Those reads and writes go over Cloudflare's D1 HTTP query API (`lib/d1-command.ts`), not a `wrangler` shell-out — no scheduling path shells a CLI, on this loop or on the availability publish. The bounds that the retired spawn carried survive as a 60 s per-request timeout and a 16 MB streamed response cap, and the endpoint is addressed per Cloudflare account, so `CLOUDFLARE_ACCOUNT_ID` is required rather than inferred.
278
278
 
279
279
  Observability: one `[calendar-booking]` line per step keyed by `bookingId` (`reconcile-claim`, `meeting-write`, `notify`, `booked`), and one `[calendar-reconcile] account=… eligibleExamined=… pendingUnreconciled=… pendingOldestAgeMin=… orphanMeetings=…` audit line per account per run. `eligibleExamined` separates "nothing to do" from "verified clean". A `pendingUnreconciled` that persists across runs means those rows are failing to become meetings rather than merely lagging — the `attempt=` count on the claim and failure lines says how many times each has been tried. `orphanMeetings` means a booking meeting has no backing eligible D1 row. `pendingOldestAgeMin` ages that backlog: a count cannot separate a short lag from a nine-hour one. The companion `[calendar-reconcile] op=liveness lastRunAgeMin=… lastAuditAgeMin=… lastSkipReason=… stale=<bool>` line is emitted every ten minutes by the platform/ui server rather than by this script, because the failure it detects is this script not reporting at all. `op=liveness-write-failed` is the one case where that record could not be written; it carries the error body and never stops the run, because a run that worked must not be reported as failed for losing its own bookkeeping.
280
280
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=availability-snapshot.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability-snapshot.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/availability-snapshot.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { writeSnapshot, MAX_ATTEMPTS, RETRY_START_DEADLINE_MS, SNAPSHOT_TABLE_DDL, SNAPSHOT_UPSERT_SQL, } from "../availability-snapshot.js";
3
+ /** A D1Client that records every statement and rejects the first `failFirstN`. */
4
+ function fakeClient(failFirstN = 0) {
5
+ const issued = [];
6
+ let failuresLeft = failFirstN;
7
+ return {
8
+ issued,
9
+ async query(dbName, sql, params = []) {
10
+ issued.push({ dbName, sql, params });
11
+ if (failuresLeft > 0) {
12
+ failuresLeft--;
13
+ throw new Error('scheduling: d1 query failed: 500\n{"errors":[{"code":7500}]}');
14
+ }
15
+ return [];
16
+ },
17
+ get statements() {
18
+ return issued.length;
19
+ },
20
+ get elapsedMs() {
21
+ return 42;
22
+ },
23
+ };
24
+ }
25
+ describe("writeSnapshot", () => {
26
+ it("issues the schema statement then the upsert, and reports one attempt", async () => {
27
+ const client = fakeClient();
28
+ const result = await writeSnapshot(client, "maxy-bookings", '{"slots":[]}', "2026-08-02T06:00:00.000Z");
29
+ expect(result.ok).toBe(true);
30
+ expect(result.attempts).toBe(1);
31
+ expect(result.elapsedMs).toBe(42);
32
+ expect(client.issued).toHaveLength(2);
33
+ expect(client.issued[0].sql).toBe(SNAPSHOT_TABLE_DDL);
34
+ expect(client.issued[1].sql).toBe(SNAPSHOT_UPSERT_SQL);
35
+ expect(client.issued[0].dbName).toBe("maxy-bookings");
36
+ });
37
+ it("binds the payload and updatedAt rather than interpolating them", async () => {
38
+ const client = fakeClient();
39
+ // A quote is the exact character the retired string-escaping path hand-handled.
40
+ const payload = `{"note":"it's fine"}`;
41
+ await writeSnapshot(client, "maxy-bookings", payload, "2026-08-02T06:00:00.000Z");
42
+ expect(client.issued[1].params).toEqual([payload, "2026-08-02T06:00:00.000Z"]);
43
+ expect(client.issued[1].sql).not.toContain("it's fine");
44
+ expect(client.issued[1].sql).toContain("?1");
45
+ expect(client.issued[1].sql).toContain("?2");
46
+ });
47
+ it("retries the whole pair once and succeeds on the second attempt", async () => {
48
+ const client = fakeClient(1);
49
+ const result = await writeSnapshot(client, "maxy-bookings", '{"slots":[]}', "2026-08-02T06:00:00.000Z");
50
+ expect(result.ok).toBe(true);
51
+ expect(result.attempts).toBe(2);
52
+ expect(result.error).toBeUndefined();
53
+ // Attempt 1 issued only the DDL before it threw; attempt 2 issued both.
54
+ expect(client.issued).toHaveLength(3);
55
+ expect(client.issued[1].sql).toBe(SNAPSHOT_TABLE_DDL);
56
+ expect(client.issued[2].sql).toBe(SNAPSHOT_UPSERT_SQL);
57
+ });
58
+ it("refuses the retry when the first attempt ran past the deadline", async () => {
59
+ const client = fakeClient(Number.MAX_SAFE_INTEGER);
60
+ // A clock that jumps the deadline between the start reading and the check.
61
+ // Without the guard a second attempt would start here and could hold the
62
+ // per-account lock into the next 300 s tick.
63
+ const readings = [0, RETRY_START_DEADLINE_MS];
64
+ let i = 0;
65
+ const now = () => readings[Math.min(i++, readings.length - 1)];
66
+ const result = await writeSnapshot(client, "maxy-bookings", '{"slots":[]}', "2026-08-02T06:00:00.000Z", now);
67
+ expect(result.ok).toBe(false);
68
+ expect(result.attempts).toBe(1);
69
+ expect(client.issued).toHaveLength(1);
70
+ });
71
+ it("gives up after MAX_ATTEMPTS and carries the last error", async () => {
72
+ const client = fakeClient(Number.MAX_SAFE_INTEGER);
73
+ const result = await writeSnapshot(client, "maxy-bookings", '{"slots":[]}', "2026-08-02T06:00:00.000Z");
74
+ expect(result.ok).toBe(false);
75
+ expect(result.attempts).toBe(MAX_ATTEMPTS);
76
+ expect(result.error).toContain("d1 query failed: 500");
77
+ expect(client.issued).toHaveLength(MAX_ATTEMPTS);
78
+ });
79
+ });
80
+ //# sourceMappingURL=availability-snapshot.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability-snapshot.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/availability-snapshot.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=publish-availability-logging.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-availability-logging.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/publish-availability-logging.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ /**
5
+ * The dispatcher's lines are its only signal, and the server spawns it with
6
+ * `stdio: 'inherit'`, so an undated line lands in server.log beside the
7
+ * server's own dated ones and cannot be attributed to a tick without counting
8
+ * neighbouring lines.
9
+ *
10
+ * Asserted against the source rather than by running the script: `main()`
11
+ * executes on import, so there is no way to import this module and observe its
12
+ * output without also performing a publish.
13
+ */
14
+ const SOURCE = readFileSync(resolve(__dirname, "../../scripts/publish-availability.ts"), "utf-8");
15
+ /** The source with comments stripped, so a comment describing the retired
16
+ * approach is never mistaken for the approach still being in use. */
17
+ const CODE = SOURCE.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
18
+ describe("publish-availability logging", () => {
19
+ it("writes to stderr in exactly one place, the dated log helper", () => {
20
+ // One call site means every emission is dated by construction: there is no
21
+ // second path to stderr for a future line to take.
22
+ expect(CODE.match(/console\.error\(/g) ?? []).toHaveLength(1);
23
+ expect(CODE).toContain("function log(line: string): void {\n console.error(`${new Date().toISOString()} ${line}`);");
24
+ });
25
+ it("no longer shells a CLI or stages a temp SQL file", () => {
26
+ expect(CODE).not.toMatch(/\bspawnSync\s*\(/);
27
+ expect(CODE).not.toMatch(/\bmkdtempSync\s*\(|\brmSync\s*\(|\btmpdir\s*\(/);
28
+ expect(CODE).not.toMatch(/["']wrangler["']/);
29
+ expect(CODE).not.toMatch(/node:child_process/);
30
+ });
31
+ });
32
+ //# sourceMappingURL=publish-availability-logging.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-availability-logging.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/publish-availability-logging.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;GASG;AACH,MAAM,MAAM,GAAG,YAAY,CACzB,OAAO,CAAC,SAAS,EAAE,uCAAuC,CAAC,EAC3D,OAAO,CACR,CAAC;AAEF;sEACsE;AACtE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;AAElF,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,2EAA2E;QAC3E,mDAAmD;QACnD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CACpB,6FAA6F,CAC9F,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,gDAAgD,CAAC,CAAC;QAC3E,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The booking page's availability snapshot: its schema statement, its upsert,
3
+ * and the bounded write that issues both.
4
+ *
5
+ * Task 2349 — this write used to be `npx wrangler d1 execute` run through
6
+ * spawnSync from the publish dispatcher. That spawn's own 60 s timeout killed a
7
+ * slow write with SIGTERM, and the failure branch logged only `status` and
8
+ * `signal` while discarding the stderr it had captured, so a killed write could
9
+ * not be told from a rejected one. Task 1805 had already replaced the same
10
+ * spawn on the reconcile loop with the D1 HTTP client and scoped this caller
11
+ * out as "not on this loop"; this completes that migration.
12
+ *
13
+ * The payload is bound, never interpolated. The retired path hand-escaped
14
+ * single quotes into the statement text, which is a correctness burden this
15
+ * transport removes rather than reimplements.
16
+ */
17
+ import type { D1Client } from "./d1-command.js";
18
+ /** Attempts one write gets. */
19
+ export declare const MAX_ATTEMPTS = 2;
20
+ /**
21
+ * How late a failed attempt may be and still earn a retry.
22
+ *
23
+ * The ceiling here is not the obvious one. `D1_TIMEOUT_MS` binds a single
24
+ * REQUEST, not an attempt, and one attempt issues up to three of them (name
25
+ * lookup, schema, upsert). Two unguarded attempts therefore reach ~300 s, which
26
+ * IS the publish interval: the run would still hold the per-account PID lock
27
+ * when the next tick spawns, that tick would skip with reason=lock-held, and a
28
+ * retry meant to save one interval would instead have cost two.
29
+ *
30
+ * A second attempt is two requests once the name lookup has cached, so 120 s.
31
+ * Refusing to start one after this point holds the whole write under ~240 s and
32
+ * guarantees the lock is released before the next tick. The realistic failures
33
+ * sit nowhere near it — a timing-out statement fails its attempt at ~60 s — so
34
+ * this forfeits no retry that was going to help.
35
+ */
36
+ export declare const RETRY_START_DEADLINE_MS = 120000;
37
+ /**
38
+ * Self-healing schema. A booking site provisioned before the snapshot feature
39
+ * has a `bookings` table but no `availability_snapshot`; issuing this first
40
+ * means the table appears on the next publish rather than waiting for a skill
41
+ * re-run.
42
+ */
43
+ export declare const SNAPSHOT_TABLE_DDL = "CREATE TABLE IF NOT EXISTS availability_snapshot (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n payload TEXT,\n updatedAt TEXT\n)";
44
+ /** One row, id 1, replaced in place on every publish. */
45
+ export declare const SNAPSHOT_UPSERT_SQL = "INSERT INTO availability_snapshot (id, payload, updatedAt)\nVALUES (1, ?1, ?2)\nON CONFLICT(id) DO UPDATE SET payload=excluded.payload, updatedAt=excluded.updatedAt";
46
+ export interface SnapshotWriteResult {
47
+ ok: boolean;
48
+ /** Attempts actually made. Never a retry that the deadline refused. */
49
+ attempts: number;
50
+ /** Total wall time spent in D1 across every attempt, the client's own figure. */
51
+ elapsedMs: number;
52
+ /** The last failure's message. Absent when ok. */
53
+ error?: string;
54
+ }
55
+ /**
56
+ * Write the snapshot, retrying the whole pair once while there is time for it.
57
+ *
58
+ * Both statements retry together rather than only the upsert: the DDL is
59
+ * IF NOT EXISTS so re-issuing it costs nothing, and one client across both
60
+ * attempts reuses its cached database-name lookup instead of paying for it
61
+ * twice.
62
+ *
63
+ * Returns a result rather than throwing so the caller's success and failure log
64
+ * lines read straight off it, carrying the same fields either way.
65
+ */
66
+ export declare function writeSnapshot(client: D1Client, dbName: string, payload: string, updatedAt: string, now?: () => number): Promise<SnapshotWriteResult>;
67
+ //# sourceMappingURL=availability-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability-snapshot.d.ts","sourceRoot":"","sources":["../../src/lib/availability-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,+BAA+B;AAC/B,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,gJAI7B,CAAC;AAEH,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,yKAEqD,CAAC;AAEtF,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EAIjB,GAAG,GAAE,MAAM,MAAiB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CAmB9B"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The booking page's availability snapshot: its schema statement, its upsert,
3
+ * and the bounded write that issues both.
4
+ *
5
+ * Task 2349 — this write used to be `npx wrangler d1 execute` run through
6
+ * spawnSync from the publish dispatcher. That spawn's own 60 s timeout killed a
7
+ * slow write with SIGTERM, and the failure branch logged only `status` and
8
+ * `signal` while discarding the stderr it had captured, so a killed write could
9
+ * not be told from a rejected one. Task 1805 had already replaced the same
10
+ * spawn on the reconcile loop with the D1 HTTP client and scoped this caller
11
+ * out as "not on this loop"; this completes that migration.
12
+ *
13
+ * The payload is bound, never interpolated. The retired path hand-escaped
14
+ * single quotes into the statement text, which is a correctness burden this
15
+ * transport removes rather than reimplements.
16
+ */
17
+ /** Attempts one write gets. */
18
+ export const MAX_ATTEMPTS = 2;
19
+ /**
20
+ * How late a failed attempt may be and still earn a retry.
21
+ *
22
+ * The ceiling here is not the obvious one. `D1_TIMEOUT_MS` binds a single
23
+ * REQUEST, not an attempt, and one attempt issues up to three of them (name
24
+ * lookup, schema, upsert). Two unguarded attempts therefore reach ~300 s, which
25
+ * IS the publish interval: the run would still hold the per-account PID lock
26
+ * when the next tick spawns, that tick would skip with reason=lock-held, and a
27
+ * retry meant to save one interval would instead have cost two.
28
+ *
29
+ * A second attempt is two requests once the name lookup has cached, so 120 s.
30
+ * Refusing to start one after this point holds the whole write under ~240 s and
31
+ * guarantees the lock is released before the next tick. The realistic failures
32
+ * sit nowhere near it — a timing-out statement fails its attempt at ~60 s — so
33
+ * this forfeits no retry that was going to help.
34
+ */
35
+ export const RETRY_START_DEADLINE_MS = 120_000;
36
+ /**
37
+ * Self-healing schema. A booking site provisioned before the snapshot feature
38
+ * has a `bookings` table but no `availability_snapshot`; issuing this first
39
+ * means the table appears on the next publish rather than waiting for a skill
40
+ * re-run.
41
+ */
42
+ export const SNAPSHOT_TABLE_DDL = `CREATE TABLE IF NOT EXISTS availability_snapshot (
43
+ id INTEGER PRIMARY KEY CHECK (id = 1),
44
+ payload TEXT,
45
+ updatedAt TEXT
46
+ )`;
47
+ /** One row, id 1, replaced in place on every publish. */
48
+ export const SNAPSHOT_UPSERT_SQL = `INSERT INTO availability_snapshot (id, payload, updatedAt)
49
+ VALUES (1, ?1, ?2)
50
+ ON CONFLICT(id) DO UPDATE SET payload=excluded.payload, updatedAt=excluded.updatedAt`;
51
+ /**
52
+ * Write the snapshot, retrying the whole pair once while there is time for it.
53
+ *
54
+ * Both statements retry together rather than only the upsert: the DDL is
55
+ * IF NOT EXISTS so re-issuing it costs nothing, and one client across both
56
+ * attempts reuses its cached database-name lookup instead of paying for it
57
+ * twice.
58
+ *
59
+ * Returns a result rather than throwing so the caller's success and failure log
60
+ * lines read straight off it, carrying the same fields either way.
61
+ */
62
+ export async function writeSnapshot(client, dbName, payload, updatedAt,
63
+ // Injectable so the retry deadline is reachable from a test rather than only
64
+ // from a real two-minute wall-clock wait. The same seam d1-command.ts gives
65
+ // its transport.
66
+ now = Date.now) {
67
+ const startedAt = now();
68
+ let lastError = "";
69
+ let attempts = 0;
70
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
71
+ attempts = attempt;
72
+ try {
73
+ await client.query(dbName, SNAPSHOT_TABLE_DDL);
74
+ await client.query(dbName, SNAPSHOT_UPSERT_SQL, [payload, updatedAt]);
75
+ return { ok: true, attempts, elapsedMs: client.elapsedMs };
76
+ }
77
+ catch (err) {
78
+ lastError = err instanceof Error ? err.message : String(err);
79
+ // Too late to start another one. Reported as the attempts actually made,
80
+ // never as MAX_ATTEMPTS, so the log line cannot claim a retry that was
81
+ // refused.
82
+ if (now() - startedAt >= RETRY_START_DEADLINE_MS)
83
+ break;
84
+ }
85
+ }
86
+ return { ok: false, attempts, elapsedMs: client.elapsedMs, error: lastError };
87
+ }
88
+ //# sourceMappingURL=availability-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability-snapshot.js","sourceRoot":"","sources":["../../src/lib/availability-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,+BAA+B;AAC/B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAE9B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAO,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;EAIhC,CAAC;AAEH,yDAAyD;AACzD,MAAM,CAAC,MAAM,mBAAmB,GAAG;;qFAEkD,CAAC;AAYtF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAgB,EAChB,MAAc,EACd,OAAe,EACf,SAAiB;AACjB,6EAA6E;AAC7E,4EAA4E;AAC5E,iBAAiB;AACjB,MAAoB,IAAI,CAAC,GAAG;IAE5B,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC;IACxB,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,YAAY,EAAE,OAAO,EAAE,EAAE,CAAC;QACzD,QAAQ,GAAG,OAAO,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;YAC/C,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,mBAAmB,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;YACtE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;QAC7D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,yEAAyE;YACzE,uEAAuE;YACvE,WAAW;YACX,IAAI,GAAG,EAAE,GAAG,SAAS,IAAI,uBAAuB;gBAAE,MAAM;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAChF,CAAC"}
@@ -18,7 +18,8 @@
18
18
  * overlapping runs. Each run records the outcome to a local last-publish.json so
19
19
  * the server's audit tick can report snapshot age independent of this job.
20
20
  *
21
- * Observability — one [calendar-availability] op=publish line per run.
21
+ * Observability — one dated [calendar-availability] op=publish line per run,
22
+ * carrying d1Ms (time in D1, the bound is 60 s) and attempt (1 or 2).
22
23
  *
23
24
  * PLATFORM_ROOT=... MAXY_UI_INTERNAL_PORT=... PUBLISH_ACCOUNT_DIR=... \
24
25
  * PUBLISH_ACCOUNT_ID=... node dist/scripts/publish-availability.js
@@ -1 +1 @@
1
- {"version":3,"file":"publish-availability.d.ts","sourceRoot":"","sources":["../../src/scripts/publish-availability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG"}
1
+ {"version":3,"file":"publish-availability.d.ts","sourceRoot":"","sources":["../../src/scripts/publish-availability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG"}