@rubytech/create-maxy-code 0.1.546 → 0.1.548

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 (292) hide show
  1. package/dist/index.js +14 -0
  2. package/package.json +1 -1
  3. package/payload/platform/lib/routine-templates/dist/index.d.ts +22 -16
  4. package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
  5. package/payload/platform/lib/routine-templates/dist/index.js +15 -10
  6. package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
  7. package/payload/platform/lib/routine-templates/dist/roster.d.ts +3 -3
  8. package/payload/platform/lib/routine-templates/dist/roster.d.ts.map +1 -1
  9. package/payload/platform/lib/routine-templates/src/__tests__/seed.test.ts +15 -11
  10. package/payload/platform/lib/routine-templates/src/index.ts +23 -17
  11. package/payload/platform/lib/routine-templates/src/roster.ts +3 -3
  12. package/payload/platform/plugins/admin/mcp/dist/index.js +32 -16
  13. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/admin/mcp/dist/tools/account-default-agent.d.ts +16 -0
  15. package/payload/platform/plugins/admin/mcp/dist/tools/account-default-agent.d.ts.map +1 -0
  16. package/payload/platform/plugins/admin/mcp/dist/tools/account-default-agent.js +21 -0
  17. package/payload/platform/plugins/admin/mcp/dist/tools/account-default-agent.js.map +1 -0
  18. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +78 -33
  19. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +11 -0
  20. package/payload/platform/plugins/docs/references/admin-ui.md +63 -8
  21. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +14 -24
  22. package/payload/platform/plugins/scheduling/PLUGIN.md +6 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/index.js +6 -2
  24. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.js +109 -1
  26. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/routine-run.test.js.map +1 -1
  27. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.d.ts +2 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.d.ts.map +1 -0
  29. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.js +54 -0
  30. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.js.map +1 -0
  31. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts +31 -0
  32. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.d.ts.map +1 -1
  33. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js +116 -2
  34. package/payload/platform/plugins/scheduling/mcp/dist/lib/routine-run.js.map +1 -1
  35. package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.d.ts +17 -0
  36. package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.d.ts.map +1 -0
  37. package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.js +43 -0
  38. package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.js.map +1 -0
  39. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.d.ts +2 -0
  40. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.d.ts.map +1 -0
  41. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.js +87 -0
  42. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.js.map +1 -0
  43. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/description-coverage.test.d.ts +2 -0
  44. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/description-coverage.test.d.ts.map +1 -0
  45. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/description-coverage.test.js +123 -0
  46. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/description-coverage.test.js.map +1 -0
  47. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +36 -0
  48. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  49. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +89 -0
  50. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  51. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +13 -1
  52. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  53. package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.d.ts +35 -0
  54. package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.d.ts.map +1 -0
  55. package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.js +56 -0
  56. package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.js.map +1 -0
  57. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +3 -2
  58. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  59. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-summary.test.d.ts +2 -0
  60. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-summary.test.d.ts.map +1 -0
  61. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-summary.test.js +80 -0
  62. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-summary.test.js.map +1 -0
  63. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +2 -1
  64. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  65. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +19 -1
  66. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  67. package/payload/platform/plugins/telegram/PLUGIN.md +2 -0
  68. package/payload/platform/plugins/telegram/mcp/dist/index.js +47 -47
  69. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  70. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts +16 -0
  71. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts.map +1 -0
  72. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js +50 -0
  73. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js.map +1 -0
  74. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts +8 -0
  75. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts.map +1 -0
  76. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js +12 -0
  77. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js.map +1 -0
  78. package/payload/platform/plugins/telegram/references/setup-guide.md +2 -0
  79. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +6 -1
  80. package/payload/platform/plugins/whatsapp/PLUGIN.md +0 -6
  81. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +2 -34
  82. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  83. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  84. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -1
  85. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  86. package/payload/platform/services/telegram-channel/dist/notification.d.ts +8 -3
  87. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  88. package/payload/platform/services/telegram-channel/dist/notification.js +10 -3
  89. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  90. package/payload/platform/services/telegram-channel/dist/server.js +1 -1
  91. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  92. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +28 -0
  93. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +1 -1
  94. package/payload/platform/services/telegram-channel/dist/turn-follow.js +33 -1
  95. package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +1 -1
  96. package/payload/platform/services/webchat-channel/dist/notification.d.ts +8 -3
  97. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  98. package/payload/platform/services/webchat-channel/dist/notification.js +10 -3
  99. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  100. package/payload/platform/services/webchat-channel/dist/server.js +1 -1
  101. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  102. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts +28 -0
  103. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -1
  104. package/payload/platform/services/webchat-channel/dist/turn-follow.js +33 -1
  105. package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -1
  106. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +42 -20
  107. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  108. package/payload/platform/services/whatsapp-channel/dist/notification.js +44 -27
  109. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  110. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  111. package/payload/platform/services/whatsapp-channel/dist/server.js +13 -5
  112. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  113. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +6 -4
  114. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  115. package/payload/platform/services/whatsapp-channel/dist/targets.js +17 -6
  116. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  117. package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +28 -0
  118. package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +1 -1
  119. package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +34 -1
  120. package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +1 -1
  121. package/payload/server/{chunk-CVUSUNXM.js → chunk-G6ULACHG.js} +2 -4
  122. package/payload/server/{chunk-X2RRMHVV.js → chunk-JOVREC6E.js} +117 -1
  123. package/payload/server/{chunk-DU7GPDJJ.js → chunk-SKYZG6OT.js} +15 -0
  124. package/payload/server/{manager-TI2YJQVZ.js → manager-2VEJKVEO.js} +2 -2
  125. package/payload/server/maxy-edge.js +2 -2
  126. package/payload/server/public/activity.html +5 -5
  127. package/payload/server/public/agents.html +4 -4
  128. package/payload/server/public/assets/{AdminLoginScreens-BGKtDqAk.js → AdminLoginScreens-Bjcs9ta9.js} +1 -1
  129. package/payload/server/public/assets/AdminLoginScreens-Bjcs9ta9.js.br +0 -0
  130. package/payload/server/public/assets/AdminLoginScreens-Bjcs9ta9.js.gz +0 -0
  131. package/payload/server/public/assets/{AdminShell-DnfgsAQl.js → AdminShell-DVBAMUCy.js} +1 -1
  132. package/payload/server/public/assets/AdminShell-DVBAMUCy.js.br +0 -0
  133. package/payload/server/public/assets/{AdminShell-DnfgsAQl.js.gz → AdminShell-DVBAMUCy.js.gz} +0 -0
  134. package/payload/server/public/assets/{activity-ByJSut0Q.js → activity-C8E8-EwQ.js} +1 -1
  135. package/payload/server/public/assets/activity-C8E8-EwQ.js.br +0 -0
  136. package/payload/server/public/assets/activity-C8E8-EwQ.js.gz +0 -0
  137. package/payload/server/public/assets/{admin-CkeJO6kA.js → admin-CeWAlpRS.js} +1 -1
  138. package/payload/server/public/assets/admin-CeWAlpRS.js.br +0 -0
  139. package/payload/server/public/assets/admin-CeWAlpRS.js.gz +0 -0
  140. package/payload/server/public/assets/{agents-D8phtRX5.js → agents-DuN92iFb.js} +1 -1
  141. package/payload/server/public/assets/agents-DuN92iFb.js.br +0 -0
  142. package/payload/server/public/assets/agents-DuN92iFb.js.gz +0 -0
  143. package/payload/server/public/assets/{browser-DQSTjdom.js → browser-BxebdKbh.js} +1 -1
  144. package/payload/server/public/assets/browser-BxebdKbh.js.br +0 -0
  145. package/payload/server/public/assets/browser-BxebdKbh.js.gz +0 -0
  146. package/payload/server/public/assets/{calendar-DFJu1XjP.js → calendar-BtFZQ5Np.js} +1 -1
  147. package/payload/server/public/assets/calendar-BtFZQ5Np.js.br +0 -0
  148. package/payload/server/public/assets/calendar-BtFZQ5Np.js.gz +0 -0
  149. package/payload/server/public/assets/chat-CGIwynaH.js +1 -0
  150. package/payload/server/public/assets/chat-CGIwynaH.js.br +3 -0
  151. package/payload/server/public/assets/chat-CGIwynaH.js.gz +0 -0
  152. package/payload/server/public/assets/chevron-left-DaNP1zDb.js +1 -0
  153. package/payload/server/public/assets/chevron-left-DaNP1zDb.js.br +0 -0
  154. package/payload/server/public/assets/chevron-right-BbbuTUnt.js +1 -0
  155. package/payload/server/public/assets/chevron-right-BbbuTUnt.js.br +0 -0
  156. package/payload/server/public/assets/clock-CfDdatjp.js +1 -0
  157. package/payload/server/public/assets/clock-CfDdatjp.js.br +0 -0
  158. package/payload/server/public/assets/clock-CfDdatjp.js.gz +0 -0
  159. package/payload/server/public/assets/{copy-lFFhXRGk.js → copy-DCDHXq3O.js} +1 -1
  160. package/payload/server/public/assets/copy-DCDHXq3O.js.br +0 -0
  161. package/payload/server/public/assets/copy-DCDHXq3O.js.gz +0 -0
  162. package/payload/server/public/assets/data-BchSWOju.js +1 -0
  163. package/payload/server/public/assets/data-BchSWOju.js.br +1 -0
  164. package/payload/server/public/assets/data-BchSWOju.js.gz +0 -0
  165. package/payload/server/public/assets/{file-text-D2Y-bkNy.js → file-text-zQgEQWCF.js} +1 -1
  166. package/payload/server/public/assets/file-text-zQgEQWCF.js.br +0 -0
  167. package/payload/server/public/assets/file-text-zQgEQWCF.js.gz +0 -0
  168. package/payload/server/public/assets/{graph-C-3yN6JD.js → graph-CFCEh_YI.js} +1 -1
  169. package/payload/server/public/assets/graph-CFCEh_YI.js.br +0 -0
  170. package/payload/server/public/assets/graph-CFCEh_YI.js.gz +0 -0
  171. package/payload/server/public/assets/{graph-labels-DiaPLP27.js → graph-labels-CA1UcDuS.js} +1 -1
  172. package/payload/server/public/assets/graph-labels-CA1UcDuS.js.br +0 -0
  173. package/payload/server/public/assets/graph-labels-CA1UcDuS.js.gz +0 -0
  174. package/payload/server/public/assets/{operator-KuGPvSa9.js → operator-hFXPDc2X.js} +1 -1
  175. package/payload/server/public/assets/operator-hFXPDc2X.js.br +0 -0
  176. package/payload/server/public/assets/operator-hFXPDc2X.js.gz +0 -0
  177. package/payload/server/public/assets/{page-BhGeFXWA.js → page-C8jyPH_3.js} +1 -1
  178. package/payload/server/public/assets/page-C8jyPH_3.js.br +0 -0
  179. package/payload/server/public/assets/page-C8jyPH_3.js.gz +0 -0
  180. package/payload/server/public/assets/{page-EnbVC-yM.js → page-ne7I_r9L.js} +1 -1
  181. package/payload/server/public/assets/page-ne7I_r9L.js.br +0 -0
  182. package/payload/server/public/assets/page-ne7I_r9L.js.gz +0 -0
  183. package/payload/server/public/assets/play-Ep0SZTbr.js +1 -0
  184. package/payload/server/public/assets/play-Ep0SZTbr.js.br +0 -0
  185. package/payload/server/public/assets/play-Ep0SZTbr.js.gz +0 -0
  186. package/payload/server/public/assets/{public-CdSgMNC7.js → public-BgnqjAal.js} +1 -1
  187. package/payload/server/public/assets/public-BgnqjAal.js.br +0 -0
  188. package/payload/server/public/assets/public-BgnqjAal.js.gz +0 -0
  189. package/payload/server/public/assets/{rotate-ccw-rTRuTqei.js → rotate-ccw-DfJWjXAg.js} +1 -1
  190. package/payload/server/public/assets/rotate-ccw-DfJWjXAg.js.br +0 -0
  191. package/payload/server/public/assets/rotate-ccw-DfJWjXAg.js.gz +0 -0
  192. package/payload/server/public/assets/routines-OGFXwTFO.js +2 -0
  193. package/payload/server/public/assets/routines-OGFXwTFO.js.br +0 -0
  194. package/payload/server/public/assets/routines-OGFXwTFO.js.gz +0 -0
  195. package/payload/server/public/assets/{skills-DFWtZNkW.js → skills-D-6EbgfP.js} +1 -1
  196. package/payload/server/public/assets/skills-D-6EbgfP.js.br +0 -0
  197. package/payload/server/public/assets/skills-D-6EbgfP.js.gz +0 -0
  198. package/payload/server/public/assets/{tasks-B94AuKSy.js → tasks-DnN5Fyds.js} +1 -1
  199. package/payload/server/public/assets/tasks-DnN5Fyds.js.br +0 -0
  200. package/payload/server/public/assets/tasks-DnN5Fyds.js.gz +0 -0
  201. package/payload/server/public/assets/{triangle-alert-DtWUg6jR.js → triangle-alert-eh9UOKDT.js} +1 -1
  202. package/payload/server/public/assets/triangle-alert-eh9UOKDT.js.br +0 -0
  203. package/payload/server/public/assets/triangle-alert-eh9UOKDT.js.gz +0 -0
  204. package/payload/server/public/assets/{useCopyFeedback-CjKYO-Qf.js → useCopyFeedback-Bz9OJHBb.js} +1 -1
  205. package/payload/server/public/assets/useCopyFeedback-Bz9OJHBb.js.br +0 -0
  206. package/payload/server/public/assets/useCopyFeedback-Bz9OJHBb.js.gz +0 -0
  207. package/payload/server/public/assets/{useMediaQuery-DlOWP56H.css → useMediaQuery-CNxsjKZp.css} +1 -1
  208. package/payload/server/public/assets/useMediaQuery-CNxsjKZp.css.br +0 -0
  209. package/payload/server/public/assets/{useMediaQuery-DlOWP56H.css.gz → useMediaQuery-CNxsjKZp.css.gz} +0 -0
  210. package/payload/server/public/assets/{useVoiceRecorder-BKFAYuzz.js → useVoiceRecorder-BaDpMYxX.js} +1 -1
  211. package/payload/server/public/assets/useVoiceRecorder-BaDpMYxX.js.br +0 -0
  212. package/payload/server/public/assets/useVoiceRecorder-BaDpMYxX.js.gz +0 -0
  213. package/payload/server/public/assets/{wrench-Ct4kf-uv.js → wrench-BO-l46rF.js} +1 -1
  214. package/payload/server/public/assets/wrench-BO-l46rF.js.br +0 -0
  215. package/payload/server/public/assets/wrench-BO-l46rF.js.gz +0 -0
  216. package/payload/server/public/browser.html +4 -4
  217. package/payload/server/public/calendar.html +8 -8
  218. package/payload/server/public/chat.html +13 -13
  219. package/payload/server/public/data.html +11 -11
  220. package/payload/server/public/graph.html +9 -9
  221. package/payload/server/public/index.html +14 -14
  222. package/payload/server/public/operator.html +14 -14
  223. package/payload/server/public/public.html +13 -13
  224. package/payload/server/public/routines.html +6 -6
  225. package/payload/server/public/skills.html +5 -5
  226. package/payload/server/public/tasks.html +6 -6
  227. package/payload/server/server.js +330 -520
  228. package/payload/server/public/assets/AdminLoginScreens-BGKtDqAk.js.br +0 -0
  229. package/payload/server/public/assets/AdminLoginScreens-BGKtDqAk.js.gz +0 -0
  230. package/payload/server/public/assets/AdminShell-DnfgsAQl.js.br +0 -0
  231. package/payload/server/public/assets/activity-ByJSut0Q.js.br +0 -0
  232. package/payload/server/public/assets/activity-ByJSut0Q.js.gz +0 -0
  233. package/payload/server/public/assets/admin-CkeJO6kA.js.br +0 -0
  234. package/payload/server/public/assets/admin-CkeJO6kA.js.gz +0 -0
  235. package/payload/server/public/assets/agents-D8phtRX5.js.br +0 -0
  236. package/payload/server/public/assets/agents-D8phtRX5.js.gz +0 -0
  237. package/payload/server/public/assets/browser-DQSTjdom.js.br +0 -0
  238. package/payload/server/public/assets/browser-DQSTjdom.js.gz +0 -0
  239. package/payload/server/public/assets/calendar-DFJu1XjP.js.br +0 -0
  240. package/payload/server/public/assets/calendar-DFJu1XjP.js.gz +0 -0
  241. package/payload/server/public/assets/chat-DUqtIsOt.js +0 -1
  242. package/payload/server/public/assets/chat-DUqtIsOt.js.br +0 -0
  243. package/payload/server/public/assets/chat-DUqtIsOt.js.gz +0 -0
  244. package/payload/server/public/assets/chevron-left-DFK18xVX.js +0 -1
  245. package/payload/server/public/assets/chevron-left-DFK18xVX.js.br +0 -0
  246. package/payload/server/public/assets/chevron-right-DJ4oaHoN.js +0 -1
  247. package/payload/server/public/assets/chevron-right-DJ4oaHoN.js.br +0 -0
  248. package/payload/server/public/assets/clock-wHIan2DW.js +0 -1
  249. package/payload/server/public/assets/clock-wHIan2DW.js.br +0 -0
  250. package/payload/server/public/assets/copy-lFFhXRGk.js.br +0 -0
  251. package/payload/server/public/assets/copy-lFFhXRGk.js.gz +0 -0
  252. package/payload/server/public/assets/data-Cxv97ARy.js +0 -1
  253. package/payload/server/public/assets/data-Cxv97ARy.js.br +0 -0
  254. package/payload/server/public/assets/data-Cxv97ARy.js.gz +0 -0
  255. package/payload/server/public/assets/file-text-D2Y-bkNy.js.br +0 -0
  256. package/payload/server/public/assets/file-text-D2Y-bkNy.js.gz +0 -0
  257. package/payload/server/public/assets/graph-C-3yN6JD.js.br +0 -0
  258. package/payload/server/public/assets/graph-C-3yN6JD.js.gz +0 -0
  259. package/payload/server/public/assets/graph-labels-DiaPLP27.js.br +0 -0
  260. package/payload/server/public/assets/graph-labels-DiaPLP27.js.gz +0 -0
  261. package/payload/server/public/assets/operator-KuGPvSa9.js.br +0 -0
  262. package/payload/server/public/assets/operator-KuGPvSa9.js.gz +0 -0
  263. package/payload/server/public/assets/page-BhGeFXWA.js.br +0 -0
  264. package/payload/server/public/assets/page-BhGeFXWA.js.gz +0 -0
  265. package/payload/server/public/assets/page-EnbVC-yM.js.br +0 -0
  266. package/payload/server/public/assets/page-EnbVC-yM.js.gz +0 -0
  267. package/payload/server/public/assets/play-jZFZAG-t.js +0 -1
  268. package/payload/server/public/assets/play-jZFZAG-t.js.br +0 -0
  269. package/payload/server/public/assets/play-jZFZAG-t.js.gz +0 -0
  270. package/payload/server/public/assets/public-CdSgMNC7.js.br +0 -0
  271. package/payload/server/public/assets/public-CdSgMNC7.js.gz +0 -0
  272. package/payload/server/public/assets/rotate-ccw-rTRuTqei.js.br +0 -0
  273. package/payload/server/public/assets/rotate-ccw-rTRuTqei.js.gz +0 -0
  274. package/payload/server/public/assets/routines-DNRxNRSQ.js +0 -2
  275. package/payload/server/public/assets/routines-DNRxNRSQ.js.br +0 -0
  276. package/payload/server/public/assets/routines-DNRxNRSQ.js.gz +0 -0
  277. package/payload/server/public/assets/skills-DFWtZNkW.js.br +0 -0
  278. package/payload/server/public/assets/skills-DFWtZNkW.js.gz +0 -0
  279. package/payload/server/public/assets/tasks-B94AuKSy.js.br +0 -0
  280. package/payload/server/public/assets/tasks-B94AuKSy.js.gz +0 -0
  281. package/payload/server/public/assets/triangle-alert-DtWUg6jR.js.br +0 -0
  282. package/payload/server/public/assets/triangle-alert-DtWUg6jR.js.gz +0 -0
  283. package/payload/server/public/assets/useCopyFeedback-CjKYO-Qf.js.br +0 -0
  284. package/payload/server/public/assets/useCopyFeedback-CjKYO-Qf.js.gz +0 -0
  285. package/payload/server/public/assets/useMediaQuery-DlOWP56H.css.br +0 -0
  286. package/payload/server/public/assets/useVoiceRecorder-BKFAYuzz.js.br +0 -0
  287. package/payload/server/public/assets/useVoiceRecorder-BKFAYuzz.js.gz +0 -0
  288. package/payload/server/public/assets/wrench-Ct4kf-uv.js.br +0 -0
  289. package/payload/server/public/assets/wrench-Ct4kf-uv.js.gz +0 -0
  290. /package/payload/server/public/assets/{useMediaQuery-BYIHo-xL.js → useMediaQuery-_mMln3Fu.js} +0 -0
  291. /package/payload/server/public/assets/{useMediaQuery-BYIHo-xL.js.br → useMediaQuery-_mMln3Fu.js.br} +0 -0
  292. /package/payload/server/public/assets/{useMediaQuery-BYIHo-xL.js.gz → useMediaQuery-_mMln3Fu.js.gz} +0 -0
@@ -0,0 +1,16 @@
1
+ export type DefaultAgentRefusal = "shape" | "not-found";
2
+ export type DefaultAgentVerdict = {
3
+ ok: true;
4
+ slug: string;
5
+ } | {
6
+ ok: false;
7
+ reason: DefaultAgentRefusal;
8
+ };
9
+ /**
10
+ * Decide whether a submitted `defaultAgent` value may be stored. An empty or
11
+ * whitespace-only value is the clear operation and is accepted as `slug: ""`.
12
+ * Shape is decided before existence, so a traversal value is refused as
13
+ * `shape` however many real files sit at the end of it.
14
+ */
15
+ export declare function checkDefaultAgent(accountDir: string, value: string): DefaultAgentVerdict;
16
+ //# sourceMappingURL=account-default-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-default-agent.d.ts","sourceRoot":"","sources":["../../src/tools/account-default-agent.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,WAAW,CAAC;AAExD,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAE/C;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAQxF"}
@@ -0,0 +1,21 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { AGENT_SLUG_PATTERN } from "../../../../../lib/agent-slug/dist/index.js";
4
+ /**
5
+ * Decide whether a submitted `defaultAgent` value may be stored. An empty or
6
+ * whitespace-only value is the clear operation and is accepted as `slug: ""`.
7
+ * Shape is decided before existence, so a traversal value is refused as
8
+ * `shape` however many real files sit at the end of it.
9
+ */
10
+ export function checkDefaultAgent(accountDir, value) {
11
+ const slug = value.trim();
12
+ if (!slug)
13
+ return { ok: true, slug: "" };
14
+ if (!AGENT_SLUG_PATTERN.test(slug))
15
+ return { ok: false, reason: "shape" };
16
+ if (!existsSync(join(accountDir, "agents", slug, "config.json"))) {
17
+ return { ok: false, reason: "not-found" };
18
+ }
19
+ return { ok: true, slug };
20
+ }
21
+ //# sourceMappingURL=account-default-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-default-agent.js","sourceRoot":"","sources":["../../src/tools/account-default-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AAqBjF;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,KAAa;IACjE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACzC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC1E,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC5B,CAAC"}
@@ -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:4566431caa948d080401758fc742c6b3f6340a0a1d17129ff51c9887836aaf87
4
+ content-hash: sha256:db7c14326a115e7d05b687b72bef0ba3892f1614ee46484e0e90414bf2c16f27
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -1686,15 +1686,13 @@ Two questions an agent cannot answer from inside itself, and both have caused re
1686
1686
 
1687
1687
  ## What wakes a session
1688
1688
 
1689
- Five things start or resume a WhatsApp session. They are distinguishable, and the distinction matters, because two of them are written by the platform rather than by the person named as the sender.
1689
+ Four things start or resume a WhatsApp session. They are distinguishable, and the distinction matters, because one of them is written by the platform rather than by the person named as the sender.
1690
1690
 
1691
1691
  **An admin phone or an account manager messages the line.** This resumes that person's own admin session. There is no per-account admin session: the session id is a hash of the account plus the person, so one person messaging from two channels resumes one thread, and two managers on one account have two threads.
1692
1692
 
1693
- **A public sender messages the line.** Three things follow, in this order, and each is independent of the ones after it.
1693
+ **A public sender messages the line.** One thing follows: the account's public agent answers the visitor, if the account has one and has enabled it. If not, the message is stored and labelled and the visitor receives nothing at all.
1694
1694
 
1695
- 1. The account's registered managers are notified, verbatim, unless the account has turned that off. This fires before any agent starts, so it survives a spawn that fails.
1696
- 2. Every bound manager's own admin session is woken with a `public-relay` turn carrying the sender's message as fenced content. This is the carrier for anything the account needs to do about the visitor: translate, put a request to a registered counterparty, carry the answer back.
1697
- 3. The account's public agent answers the visitor, if the account has one and has enabled it. If not, the visitor receives nothing at all, and steps 1 and 2 have already happened regardless.
1695
+ Nobody is told the visitor wrote. The platform used to notify the registered managers and to wake each of them with a relay turn; both were retired, because telling the operator a stranger wrote in is a scheduled routine's job. A routine chooses its own recipient, its own wording and its own transport, and an account that wants no such message simply does not run one.
1698
1696
 
1699
1697
  **The scheduler fires a due event.** This raises a `schedule` turn on the destination's own session, carrying the event's prompt.
1700
1698
 
@@ -1702,19 +1700,16 @@ Five things start or resume a WhatsApp session. They are distinguishable, and th
1702
1700
 
1703
1701
  ## Reading why you woke
1704
1702
 
1705
- Every turn carries a `source`. Three values exist:
1703
+ Every turn carries a `source`. Two values exist:
1706
1704
 
1707
1705
  | `source` | Meaning |
1708
1706
  |---|---|
1709
1707
  | `user` | A real person sent this message to the line. Also the value when the field is absent. |
1710
1708
  | `schedule` | The scheduler wrote this turn. The named destination did not send it. |
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. |
1712
1709
 
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.
1710
+ **A public visitor's turn is the one turn whose `## Instruction` block is the account's own.** Its body is `agents/<slug>/INSTRUCTION.md`, written by the account owner, where `<slug>` is the public agent answering. If that file is missing the turn is refused outright, so the visitor is not answered rather than being handed platform prose the account cannot edit. Every turn you authored, from your own handset or through the scheduler, takes the platform's own directive and never reads that file, so it can never be refused over a file it does not use.
1714
1711
 
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.
1716
-
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.
1712
+ On every turn on this line the platform forwards your closing text to the sender when you called no reply tool, so a turn that ends with an answer written but no tool called still reaches the person waiting.
1718
1713
 
1719
1714
  ## Which files compose the prompt
1720
1715
 
@@ -1725,25 +1720,22 @@ The system prompt is assembled per spawn from one agent directory, and **which d
1725
1720
 
1726
1721
  From that one directory it takes `IDENTITY.md` and `SOUL.md`, verbatim.
1727
1722
 
1728
- The consequence catches people out: **an instruction written into `agents/admin/SOUL.md` is never read by a public spawn**, and an instruction written into a public agent's `SOUL.md` is never read by an admin turn. Before writing an instruction into a role file, decide which role will actually be running when it needs to apply. If the answer is "the admin agent, woken by a stranger's message", the file is `agents/admin/SOUL.md` and the trigger is the `public-relay` dispatch above.
1723
+ The consequence catches people out: **an instruction written into `agents/admin/SOUL.md` is never read by a public spawn**, and an instruction written into a public agent's `SOUL.md` is never read by an admin turn. Before writing an instruction into a role file, decide which role will actually be running when it needs to apply. A stranger's message wakes the public agent and nothing else, so an instruction meant to govern what happens when a stranger writes belongs in that agent's own files, never in `agents/admin/`.
1729
1724
 
1730
1725
  A public agent's directory also carries `KNOWLEDGE.md` and `config.json`. An account can hold as many public agents as it likes; only one is bound to WhatsApp at a time, and that binding is per account.
1731
1726
 
1732
1727
  ## The switches, and what each one stops
1733
1728
 
1734
- Five settings sit on this path. They are not interchangeable, and reaching for the wrong one is how an operator ends up silencing more than they meant.
1729
+ Four settings sit on this path. They are not interchangeable, and reaching for the wrong one is how an operator ends up silencing more than they meant.
1735
1730
 
1736
1731
  | Setting | What it stops | What it leaves running |
1737
1732
  |---|---|---|
1738
1733
  | `dmPolicy: disabled` | Public senders are refused at the gate. | Admin and manager inbound. |
1739
- | `publicAgentEnabled: false` | The visitor gets no reply. | The manager notification and the relay. |
1740
- | `managerNotifyEnabled: false` | The verbatim copy of the visitor's message. | The relay, and any reply the woken turn sends. |
1734
+ | `publicAgentEnabled: false` | The visitor gets no reply. | Storage and labelling of what the visitor sent. |
1741
1735
  | `dispatchInbound: false` | Every agent turn on that account's socket. | Storage: messages are still stored and readable. |
1742
- | `dispatch: false` on one registered party | The public-agent turn for that one phone. | Everything else: storage, the label, the manager notification, the relay, and every other sender on the line. |
1743
-
1744
- `managerNotifyEnabled` is the fine-grained one, and the one to reach for when the notifications are unwanted but the work is not. Unbinding a manager is not a substitute: the manager registry is shared by the notification, the relay and `whatsapp-notify-manager`, so unbinding silences all three.
1736
+ | `dispatch: false` on one registered party | The public-agent turn for that one phone. | Everything else: storage, the label, and every other sender on the line. |
1745
1737
 
1746
- Read the current value with `whatsapp-config {action:'get-manager-notify-enabled'}` and set it with `set-manager-notify-enabled`. Both are scoped to the calling session's own account.
1738
+ None of these governs whether the operator hears about a visitor, because no setting does: that is a scheduled routine's job, and the way to stop it is to stop the routine. The manager registry is read by nothing on this path at all, so unbinding a manager changes nothing a visitor's message does.
1747
1739
 
1748
1740
  ## When the answer is not here
1749
1741
 
@@ -1753,16 +1745,14 @@ Read the current value with `whatsapp-config {action:'get-manager-notify-enabled
1753
1745
  [whatsapp:wire] op=stanza msgId=<id>
1754
1746
  [wa-notify] op=eligible msgId=<id> reason=<how the sender was admitted>
1755
1747
  [whatsapp:route] op=routed inboundId=<id> msgIds=<the stanzas this payload covers>
1756
- [whatsapp:notify] op=public-inbound inboundId=<id> enabled=0|1 outcome=sent|failed|no-recipients|disabled
1757
- [whatsapp:relay] op=dispatch inboundId=<id> outcome=sent|failed|no-recipients
1758
1748
  [whatsapp:party] op=dispatch-decision inboundId=<id> party=… dispatch=true|false spawned=0|1 (labelled senders only)
1759
1749
  [whatsapp:public-agent] op=route inboundId=<id> enabled=… slug=… spawned=0|1
1760
1750
  [whatsapp:public-agent] op=spawn-ok | op=spawn-failed inboundId=<id>
1761
1751
  ```
1762
1752
 
1763
- Follow one inbound by its `inboundId`. That selects the last five lines and nothing else, however many visitors messaged in the same window. For the first two, read `msgIds=` off the `op=routed` line and grep one of those ids. Two greps rather than one, because the debouncer merges stanzas into a single routed payload and the two halves are keyed either side of that merge.
1753
+ Follow one inbound by its `inboundId`. That selects the last four lines and nothing else, however many visitors messaged in the same window. For the first two, read `msgIds=` off the `op=routed` line and grep one of those ids. Two greps rather than one, because the debouncer merges stanzas into a single routed payload and the two halves are keyed either side of that merge.
1764
1754
 
1765
- Anything that failed carries the same id: the drop lines, `op=public-inbound-rejected`, `op=dispatch-rejected` and `op=spawn-failed`. Fewer lines than expected, with none of them a failure, means a step emitted nothing at all — which is what the standing audits below are for.
1755
+ Anything that failed carries the same id: the drop lines and `op=spawn-failed`. Fewer lines than expected, with none of them a failure, means a step emitted nothing at all — which is what the standing audits below are for.
1766
1756
 
1767
1757
  Do not try to follow an inbound by its sender. `sender=` appears on two of these lines, and the same visitor is a LID on `op=stanza` and an E.164 on `op=routed`, so no sender-based filter spans the merge.
1768
1758
 
@@ -1777,7 +1767,7 @@ op=startup-watch state=disposed
1777
1767
 
1778
1768
  The first-run dialog is answered for the session, and the watcher that guards against an *unanswered* one drops what it saw before that answer. `bufferCleared=false` means the confirm fired and the drop did not, which is the shape that killed a resumed spawn on 2026-08-01: the answered dialog's own footer stayed in the buffer, the replayed transcript slid past it, and the spawn was blocked by a line the agent itself had written the day before.
1779
1769
 
1780
- Three standing audits reconcile that chain rather than waiting for someone to notice. `op=public-notify-reconcile` reports `gap`, `relayGap` and `answerGap`, each of which counts a step that a routed inbound reached and the next one did not. `op=spawn-census` reports attempts against successes. A non-zero gap, or attempts with no successes, is a fault even when nothing has been reported.
1770
+ Two standing audits reconcile that chain rather than waiting for someone to notice. `op=public-notify-reconcile` reports `routed` against `answered`, so a visitor routed to an agent that never replied shows as `answerGap`. `op=spawn-census` reports attempts against successes. A non-zero gap, or attempts with no successes, is a fault even when nothing has been reported.
1781
1771
 
1782
1772
  ---
1783
1773
  # Settings
@@ -3494,13 +3484,27 @@ The Routines surface (`/routines`) is the admin view of the account's
3494
3484
  automation `:Event` nodes — the device's scheduled agent turns and tool
3495
3485
  dispatches, which the Calendar surface deliberately excludes (`calendar.ts`
3496
3486
  unions only appointment events, `actionPlugin IS NULL`). It renders one card per
3497
- routine (title, the roster's one-sentence description where the routine has one,
3498
- human-readable schedule, next run, channel/destination or tool, status); a card
3499
- opens a detail modal modeled on the calendar `EventDetailModal`. The description
3500
- is roster-owned: the seed is its only writer, `GET /api/admin/routines` projects
3501
- it read-only, and the PATCH route refuses it, so an operator-created routine has
3502
- none and its card renders with no line and no placeholder.
3503
- The modal edits the prose instruction (`agentPrompt`), the dispatch binding
3487
+ routine (title, its one-line description where it has one, human-readable
3488
+ schedule, next run, channel/destination or tool, status); a card opens a detail
3489
+ modal modeled on the calendar `EventDetailModal`. A shipped routine is seeded with
3490
+ a description and the operator can rewrite it from the modal; a routine nobody has
3491
+ captioned renders with no line and no placeholder. Editing it stamps the row
3492
+ operator-owned, so the next roster refresh leaves the operator's wording alone,
3493
+ and recomputes the row's search vector, which is built from the name and the
3494
+ description. A description is never dispatched: `agentPrompt` is the only text a
3495
+ fire sends.
3496
+ The read-only view shows two prose blocks, each under its own heading, in the
3497
+ order **Description** then **Instruction sent to the agent**. Both use the same
3498
+ header-above-prose shape, so neither reads as a different kind of thing from the
3499
+ other. Before this the modal showed one unheaded block, so the caption and the
3500
+ multi-KB prompt that actually fires were indistinguishable. The instruction
3501
+ heading never reads the bare word "Instruction", in either mode: that collides
3502
+ with the `## Instruction` block the platform appends to every dispatched channel
3503
+ turn, and an agent read the two as the same field. The Description block renders
3504
+ on every kind of routine, including a tool-action routine and a bare trigger,
3505
+ because both already show it on the card; the instruction block belongs to an
3506
+ agent routine, the only kind that has one. The modal edits the description, the
3507
+ prose instruction (`agentPrompt`), the dispatch binding
3504
3508
  (`agentChannel` and `agentDestination`, chosen from a server-supplied roster
3505
3509
  rather than typed), and the schedule via a friendly builder (frequency
3506
3510
  daily/weekly/monthly, day-of-week, day-of-month, time) that falls back to a
@@ -3521,6 +3525,29 @@ render it through one `RoutineStatusDot`, which is what stops them drifting:
3521
3525
  | `cancelled`, `completed` | solid grey (`--text-tertiary`) | deliberately stopped or finished |
3522
3526
  | anything else, and `null` | grey ring, no fill | no reading taken |
3523
3527
 
3528
+ **The run recap.** Each card carries a history icon that opens that routine's
3529
+ run history; the detail modal's **Runs** button reaches the same view. A run
3530
+ shows what it sent — the message text itself, on the row and in full in the run
3531
+ dialog — and a run that finished having sent nothing says so, which is a
3532
+ different statement from a run whose outcome is not yet known. Both the routine
3533
+ list and the run list have a Refresh control, because a run is promoted from
3534
+ "accepted" to "finished" by a background sweep a couple of minutes later. The
3535
+ **Download all runs** file carries the message text as its last column, so an
3536
+ export of a customer-facing routine contains what was said to customers.
3537
+
3538
+ The run's own dot answers a different question from the routine's:
3539
+
3540
+ | run status | dot | meaning |
3541
+ |---|---|---|
3542
+ | `finished` | green (`--session-live`) | it ran and its output was seen |
3543
+ | `open`, `accepted`, `started` | activity (`--color-alias-status-activity`) | in flight, or accepted with nothing yet known |
3544
+ | `no-destination` | amber (`--color-alias-status-warning-solid`) | it had nowhere to send |
3545
+ | `failed` | red (`--color-alias-status-error-solid`) | the dispatch failed |
3546
+ | anything else, and `null` | grey ring, no fill | no reading taken |
3547
+
3548
+ `app/routines/types.ts` holds both mappings and `RunStatusDot` is the only thing
3549
+ that paints the run one, so the row and the dialog cannot drift.
3550
+
3524
3551
  The unrecognised case is a state of its own, never a fallback into green: `GET
3525
3552
  /api/admin/routines` projects `eventStatus` verbatim, so a value outside the set
3526
3553
  can reach the card at any time, and a stood-down routine showing green is a
@@ -3538,7 +3565,7 @@ The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.t
3538
3565
  | `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
3539
3566
  | `GET /api/admin/routines/:eventId/runs` | One routine's `:RoutineRun` history, newest first. `limit` clamps to 1..500 and defaults to 50, so a caller that wants the full page must ask for it. |
3540
3567
  | `GET /api/admin/routines/:eventId/runs.csv` | The same projection as a `text/csv` attachment with `Content-Disposition`, and **no** limit, so it is not the same row set as a clamped `/runs` call. Re-parses its own output and returns 500 rather than serving a document with fewer rows than the query returned. Zero rows is 404, not a header-only file: the query cannot tell a routine with no runs from an `eventId` on another account. |
3541
- | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `recurrence`, and the `agentChannel`/`agentDestination` pair. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
3568
+ | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `description`, `recurrence`, and the `agentChannel`/`agentDestination` pair. A `description` write stamps `operatorEdited` and recomputes the row's `embedding` from `Event: <name>` plus the new text, reading the name from the row rather than the request body; the embedder returning nothing leaves the previous vector in place and the save still succeeds. A whitespace-only `description` is stored as null, so "no caption" is one state in the graph. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
3542
3569
  | `GET /api/admin/dispatch-destinations` | Every destination the session's account may bind a routine to, across both channels, each labelled by the authoritative list it came from (`House admin`, `Account manager`, `Telegram admin`), plus an optional `name`: a display-only person name resolved from `:Person` in the row's own account scope — the house for a `House admin` row, the managed sub-account for an `Account manager` row, and never for Telegram, whose destinations are chat ids. Computed from the same lists the validator reads and filtered by the same rule, so the picker cannot offer what the save refuses; `name` is never part of what Save posts, and a graph outage returns an unnamed roster rather than an error. |
3543
3570
  | `POST /api/admin/routines/:eventId/suspend` | Writes `eventStatus = 'suspended'` on a routine currently `'scheduled'`. 404s otherwise, so a second suspend is not a silent no-op. |
3544
3571
  | `POST /api/admin/routines/:eventId/resume` | Returns a suspended routine to `'scheduled'`. 404s if it is not suspended, 409s if it cannot be resumed. |
@@ -3619,6 +3646,24 @@ would say so. `completed` gets its own bucket even though no code path writes it
3619
3646
  today, because it remains part of the shipped enum and one row carrying it would
3620
3647
  otherwise pin `other` above zero forever and kill the alarm.
3621
3648
 
3649
+ `[schedule-audit] op=description-coverage acct=<8> routines=<n>
3650
+ withDescription=<n> withInstruction=<n> toolActions=<n>` is the pair behind the
3651
+ two prose blocks the modal shows. A routine carrying an instruction and no
3652
+ description is a card whose title is all the operator gets; one carrying a
3653
+ description and no instruction is worse, because it looks explained, fires on
3654
+ schedule and sends nothing. Neither state writes an event of its own, so only a
3655
+ periodic count reveals them, and the counts sit on one line beside the total
3656
+ because any one of them alone answers the wrong question. `toolActions` is on the
3657
+ line because `withInstruction` is otherwise comparable to nothing: a tool-action
3658
+ routine has no instruction and is not silent, it runs a tool, so an account
3659
+ holding one would sit at `routines > withInstruction` for ever and the alarm
3660
+ would be dead from that point on. The genuinely silent count is `routines -
3661
+ toolActions - withInstruction`, the same reconciliation `other` performs in the
3662
+ suspension census. Emitted per account on every sweep tick including a healthy
3663
+ one, over the same automation set the routes list. The roster seed's own
3664
+ `descNull` counter is its narrow twin, scoped to seeded rows and so blind to
3665
+ exactly the operator-created ones most likely to carry no caption.
3666
+
3622
3667
  **The shipped roster.** Every account starts with eleven routines —
3623
3668
  `start-of-day`, `inbound-check`, `end-of-day`, `appointment-reminder`,
3624
3669
  `commitment-chase`, `calendar-reconcile`, `contact-reconcile`, `crm-reconcile`,
@@ -9,6 +9,17 @@ 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.548)
13
+
14
+ - Telegram now works per account: each bot has its own address and secret, a scheduled message goes to the right account, and a check reports any bot that is set up but never delivers.
15
+ - Every WhatsApp reply and document send now names the account it belongs to, and a send whose account does not match its conversation is refused.
16
+ - Each turn now records how much the assistant actually had to work with, so a turn that came back thin can be told apart from one that failed.
17
+
18
+ ## 2026-08-02 (0.1.547)
19
+
20
+ - A routine's past runs now read as cards showing status, channel and the message actually sent, with a refresh and a way back in. Runs with no captured output are flagged.
21
+ - The separate notification and relay that fired when someone outside your team messaged you have been retired. The public agent's own reply is now the record, and the count of routed and answered messages comes from it.
22
+
12
23
  ## 2026-08-02 (0.1.546)
13
24
 
14
25
  - When the assistant notifies you on a channel, the outcome of that notification is now recorded, so a message that never arrived can be traced rather than guessed at.
@@ -99,13 +99,27 @@ The Routines surface (`/routines`) is the admin view of the account's
99
99
  automation `:Event` nodes — the device's scheduled agent turns and tool
100
100
  dispatches, which the Calendar surface deliberately excludes (`calendar.ts`
101
101
  unions only appointment events, `actionPlugin IS NULL`). It renders one card per
102
- routine (title, the roster's one-sentence description where the routine has one,
103
- human-readable schedule, next run, channel/destination or tool, status); a card
104
- opens a detail modal modeled on the calendar `EventDetailModal`. The description
105
- is roster-owned: the seed is its only writer, `GET /api/admin/routines` projects
106
- it read-only, and the PATCH route refuses it, so an operator-created routine has
107
- none and its card renders with no line and no placeholder.
108
- The modal edits the prose instruction (`agentPrompt`), the dispatch binding
102
+ routine (title, its one-line description where it has one, human-readable
103
+ schedule, next run, channel/destination or tool, status); a card opens a detail
104
+ modal modeled on the calendar `EventDetailModal`. A shipped routine is seeded with
105
+ a description and the operator can rewrite it from the modal; a routine nobody has
106
+ captioned renders with no line and no placeholder. Editing it stamps the row
107
+ operator-owned, so the next roster refresh leaves the operator's wording alone,
108
+ and recomputes the row's search vector, which is built from the name and the
109
+ description. A description is never dispatched: `agentPrompt` is the only text a
110
+ fire sends.
111
+ The read-only view shows two prose blocks, each under its own heading, in the
112
+ order **Description** then **Instruction sent to the agent**. Both use the same
113
+ header-above-prose shape, so neither reads as a different kind of thing from the
114
+ other. Before this the modal showed one unheaded block, so the caption and the
115
+ multi-KB prompt that actually fires were indistinguishable. The instruction
116
+ heading never reads the bare word "Instruction", in either mode: that collides
117
+ with the `## Instruction` block the platform appends to every dispatched channel
118
+ turn, and an agent read the two as the same field. The Description block renders
119
+ on every kind of routine, including a tool-action routine and a bare trigger,
120
+ because both already show it on the card; the instruction block belongs to an
121
+ agent routine, the only kind that has one. The modal edits the description, the
122
+ prose instruction (`agentPrompt`), the dispatch binding
109
123
  (`agentChannel` and `agentDestination`, chosen from a server-supplied roster
110
124
  rather than typed), and the schedule via a friendly builder (frequency
111
125
  daily/weekly/monthly, day-of-week, day-of-month, time) that falls back to a
@@ -126,6 +140,29 @@ render it through one `RoutineStatusDot`, which is what stops them drifting:
126
140
  | `cancelled`, `completed` | solid grey (`--text-tertiary`) | deliberately stopped or finished |
127
141
  | anything else, and `null` | grey ring, no fill | no reading taken |
128
142
 
143
+ **The run recap.** Each card carries a history icon that opens that routine's
144
+ run history; the detail modal's **Runs** button reaches the same view. A run
145
+ shows what it sent — the message text itself, on the row and in full in the run
146
+ dialog — and a run that finished having sent nothing says so, which is a
147
+ different statement from a run whose outcome is not yet known. Both the routine
148
+ list and the run list have a Refresh control, because a run is promoted from
149
+ "accepted" to "finished" by a background sweep a couple of minutes later. The
150
+ **Download all runs** file carries the message text as its last column, so an
151
+ export of a customer-facing routine contains what was said to customers.
152
+
153
+ The run's own dot answers a different question from the routine's:
154
+
155
+ | run status | dot | meaning |
156
+ |---|---|---|
157
+ | `finished` | green (`--session-live`) | it ran and its output was seen |
158
+ | `open`, `accepted`, `started` | activity (`--color-alias-status-activity`) | in flight, or accepted with nothing yet known |
159
+ | `no-destination` | amber (`--color-alias-status-warning-solid`) | it had nowhere to send |
160
+ | `failed` | red (`--color-alias-status-error-solid`) | the dispatch failed |
161
+ | anything else, and `null` | grey ring, no fill | no reading taken |
162
+
163
+ `app/routines/types.ts` holds both mappings and `RunStatusDot` is the only thing
164
+ that paints the run one, so the row and the dialog cannot drift.
165
+
129
166
  The unrecognised case is a state of its own, never a fallback into green: `GET
130
167
  /api/admin/routines` projects `eventStatus` verbatim, so a value outside the set
131
168
  can reach the card at any time, and a stood-down routine showing green is a
@@ -143,7 +180,7 @@ The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.t
143
180
  | `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
144
181
  | `GET /api/admin/routines/:eventId/runs` | One routine's `:RoutineRun` history, newest first. `limit` clamps to 1..500 and defaults to 50, so a caller that wants the full page must ask for it. |
145
182
  | `GET /api/admin/routines/:eventId/runs.csv` | The same projection as a `text/csv` attachment with `Content-Disposition`, and **no** limit, so it is not the same row set as a clamped `/runs` call. Re-parses its own output and returns 500 rather than serving a document with fewer rows than the query returned. Zero rows is 404, not a header-only file: the query cannot tell a routine with no runs from an `eventId` on another account. |
146
- | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `recurrence`, and the `agentChannel`/`agentDestination` pair. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
183
+ | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `description`, `recurrence`, and the `agentChannel`/`agentDestination` pair. A `description` write stamps `operatorEdited` and recomputes the row's `embedding` from `Event: <name>` plus the new text, reading the name from the row rather than the request body; the embedder returning nothing leaves the previous vector in place and the save still succeeds. A whitespace-only `description` is stored as null, so "no caption" is one state in the graph. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
147
184
  | `GET /api/admin/dispatch-destinations` | Every destination the session's account may bind a routine to, across both channels, each labelled by the authoritative list it came from (`House admin`, `Account manager`, `Telegram admin`), plus an optional `name`: a display-only person name resolved from `:Person` in the row's own account scope — the house for a `House admin` row, the managed sub-account for an `Account manager` row, and never for Telegram, whose destinations are chat ids. Computed from the same lists the validator reads and filtered by the same rule, so the picker cannot offer what the save refuses; `name` is never part of what Save posts, and a graph outage returns an unnamed roster rather than an error. |
148
185
  | `POST /api/admin/routines/:eventId/suspend` | Writes `eventStatus = 'suspended'` on a routine currently `'scheduled'`. 404s otherwise, so a second suspend is not a silent no-op. |
149
186
  | `POST /api/admin/routines/:eventId/resume` | Returns a suspended routine to `'scheduled'`. 404s if it is not suspended, 409s if it cannot be resumed. |
@@ -224,6 +261,24 @@ would say so. `completed` gets its own bucket even though no code path writes it
224
261
  today, because it remains part of the shipped enum and one row carrying it would
225
262
  otherwise pin `other` above zero forever and kill the alarm.
226
263
 
264
+ `[schedule-audit] op=description-coverage acct=<8> routines=<n>
265
+ withDescription=<n> withInstruction=<n> toolActions=<n>` is the pair behind the
266
+ two prose blocks the modal shows. A routine carrying an instruction and no
267
+ description is a card whose title is all the operator gets; one carrying a
268
+ description and no instruction is worse, because it looks explained, fires on
269
+ schedule and sends nothing. Neither state writes an event of its own, so only a
270
+ periodic count reveals them, and the counts sit on one line beside the total
271
+ because any one of them alone answers the wrong question. `toolActions` is on the
272
+ line because `withInstruction` is otherwise comparable to nothing: a tool-action
273
+ routine has no instruction and is not silent, it runs a tool, so an account
274
+ holding one would sit at `routines > withInstruction` for ever and the alarm
275
+ would be dead from that point on. The genuinely silent count is `routines -
276
+ toolActions - withInstruction`, the same reconciliation `other` performs in the
277
+ suspension census. Emitted per account on every sweep tick including a healthy
278
+ one, over the same automation set the routes list. The roster seed's own
279
+ `descNull` counter is its narrow twin, scoped to seeded rows and so blind to
280
+ exactly the operator-created ones most likely to carry no caption.
281
+
227
282
  **The shipped roster.** Every account starts with eleven routines —
228
283
  `start-of-day`, `inbound-check`, `end-of-day`, `appointment-reminder`,
229
284
  `commitment-chase`, `calendar-reconcile`, `contact-reconcile`, `crm-reconcile`,
@@ -4,15 +4,13 @@ Two questions an agent cannot answer from inside itself, and both have caused re
4
4
 
5
5
  ## What wakes a session
6
6
 
7
- Five things start or resume a WhatsApp session. They are distinguishable, and the distinction matters, because two of them are written by the platform rather than by the person named as the sender.
7
+ Four things start or resume a WhatsApp session. They are distinguishable, and the distinction matters, because one of them is written by the platform rather than by the person named as the sender.
8
8
 
9
9
  **An admin phone or an account manager messages the line.** This resumes that person's own admin session. There is no per-account admin session: the session id is a hash of the account plus the person, so one person messaging from two channels resumes one thread, and two managers on one account have two threads.
10
10
 
11
- **A public sender messages the line.** Three things follow, in this order, and each is independent of the ones after it.
11
+ **A public sender messages the line.** One thing follows: the account's public agent answers the visitor, if the account has one and has enabled it. If not, the message is stored and labelled and the visitor receives nothing at all.
12
12
 
13
- 1. The account's registered managers are notified, verbatim, unless the account has turned that off. This fires before any agent starts, so it survives a spawn that fails.
14
- 2. Every bound manager's own admin session is woken with a `public-relay` turn carrying the sender's message as fenced content. This is the carrier for anything the account needs to do about the visitor: translate, put a request to a registered counterparty, carry the answer back.
15
- 3. The account's public agent answers the visitor, if the account has one and has enabled it. If not, the visitor receives nothing at all, and steps 1 and 2 have already happened regardless.
13
+ Nobody is told the visitor wrote. The platform used to notify the registered managers and to wake each of them with a relay turn; both were retired, because telling the operator a stranger wrote in is a scheduled routine's job. A routine chooses its own recipient, its own wording and its own transport, and an account that wants no such message simply does not run one.
16
14
 
17
15
  **The scheduler fires a due event.** This raises a `schedule` turn on the destination's own session, carrying the event's prompt.
18
16
 
@@ -20,19 +18,16 @@ Five things start or resume a WhatsApp session. They are distinguishable, and th
20
18
 
21
19
  ## Reading why you woke
22
20
 
23
- Every turn carries a `source`. Three values exist:
21
+ Every turn carries a `source`. Two values exist:
24
22
 
25
23
  | `source` | Meaning |
26
24
  |---|---|
27
25
  | `user` | A real person sent this message to the line. Also the value when the field is absent. |
28
26
  | `schedule` | The scheduler wrote this turn. The named destination did not send it. |
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
27
 
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.
28
+ **A public visitor's turn is the one turn whose `## Instruction` block is the account's own.** Its body is `agents/<slug>/INSTRUCTION.md`, written by the account owner, where `<slug>` is the public agent answering. If that file is missing the turn is refused outright, so the visitor is not answered rather than being handed platform prose the account cannot edit. Every turn you authored, from your own handset or through the scheduler, takes the platform's own directive and never reads that file, so it can never be refused over a file it does not use.
32
29
 
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.
34
-
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.
30
+ On every turn on this line the platform forwards your closing text to the sender when you called no reply tool, so a turn that ends with an answer written but no tool called still reaches the person waiting.
36
31
 
37
32
  ## Which files compose the prompt
38
33
 
@@ -43,25 +38,22 @@ The system prompt is assembled per spawn from one agent directory, and **which d
43
38
 
44
39
  From that one directory it takes `IDENTITY.md` and `SOUL.md`, verbatim.
45
40
 
46
- The consequence catches people out: **an instruction written into `agents/admin/SOUL.md` is never read by a public spawn**, and an instruction written into a public agent's `SOUL.md` is never read by an admin turn. Before writing an instruction into a role file, decide which role will actually be running when it needs to apply. If the answer is "the admin agent, woken by a stranger's message", the file is `agents/admin/SOUL.md` and the trigger is the `public-relay` dispatch above.
41
+ The consequence catches people out: **an instruction written into `agents/admin/SOUL.md` is never read by a public spawn**, and an instruction written into a public agent's `SOUL.md` is never read by an admin turn. Before writing an instruction into a role file, decide which role will actually be running when it needs to apply. A stranger's message wakes the public agent and nothing else, so an instruction meant to govern what happens when a stranger writes belongs in that agent's own files, never in `agents/admin/`.
47
42
 
48
43
  A public agent's directory also carries `KNOWLEDGE.md` and `config.json`. An account can hold as many public agents as it likes; only one is bound to WhatsApp at a time, and that binding is per account.
49
44
 
50
45
  ## The switches, and what each one stops
51
46
 
52
- Five settings sit on this path. They are not interchangeable, and reaching for the wrong one is how an operator ends up silencing more than they meant.
47
+ Four settings sit on this path. They are not interchangeable, and reaching for the wrong one is how an operator ends up silencing more than they meant.
53
48
 
54
49
  | Setting | What it stops | What it leaves running |
55
50
  |---|---|---|
56
51
  | `dmPolicy: disabled` | Public senders are refused at the gate. | Admin and manager inbound. |
57
- | `publicAgentEnabled: false` | The visitor gets no reply. | The manager notification and the relay. |
58
- | `managerNotifyEnabled: false` | The verbatim copy of the visitor's message. | The relay, and any reply the woken turn sends. |
52
+ | `publicAgentEnabled: false` | The visitor gets no reply. | Storage and labelling of what the visitor sent. |
59
53
  | `dispatchInbound: false` | Every agent turn on that account's socket. | Storage: messages are still stored and readable. |
60
- | `dispatch: false` on one registered party | The public-agent turn for that one phone. | Everything else: storage, the label, the manager notification, the relay, and every other sender on the line. |
61
-
62
- `managerNotifyEnabled` is the fine-grained one, and the one to reach for when the notifications are unwanted but the work is not. Unbinding a manager is not a substitute: the manager registry is shared by the notification, the relay and `whatsapp-notify-manager`, so unbinding silences all three.
54
+ | `dispatch: false` on one registered party | The public-agent turn for that one phone. | Everything else: storage, the label, and every other sender on the line. |
63
55
 
64
- Read the current value with `whatsapp-config {action:'get-manager-notify-enabled'}` and set it with `set-manager-notify-enabled`. Both are scoped to the calling session's own account.
56
+ None of these governs whether the operator hears about a visitor, because no setting does: that is a scheduled routine's job, and the way to stop it is to stop the routine. The manager registry is read by nothing on this path at all, so unbinding a manager changes nothing a visitor's message does.
65
57
 
66
58
  ## When the answer is not here
67
59
 
@@ -71,16 +63,14 @@ Read the current value with `whatsapp-config {action:'get-manager-notify-enabled
71
63
  [whatsapp:wire] op=stanza msgId=<id>
72
64
  [wa-notify] op=eligible msgId=<id> reason=<how the sender was admitted>
73
65
  [whatsapp:route] op=routed inboundId=<id> msgIds=<the stanzas this payload covers>
74
- [whatsapp:notify] op=public-inbound inboundId=<id> enabled=0|1 outcome=sent|failed|no-recipients|disabled
75
- [whatsapp:relay] op=dispatch inboundId=<id> outcome=sent|failed|no-recipients
76
66
  [whatsapp:party] op=dispatch-decision inboundId=<id> party=… dispatch=true|false spawned=0|1 (labelled senders only)
77
67
  [whatsapp:public-agent] op=route inboundId=<id> enabled=… slug=… spawned=0|1
78
68
  [whatsapp:public-agent] op=spawn-ok | op=spawn-failed inboundId=<id>
79
69
  ```
80
70
 
81
- Follow one inbound by its `inboundId`. That selects the last five lines and nothing else, however many visitors messaged in the same window. For the first two, read `msgIds=` off the `op=routed` line and grep one of those ids. Two greps rather than one, because the debouncer merges stanzas into a single routed payload and the two halves are keyed either side of that merge.
71
+ Follow one inbound by its `inboundId`. That selects the last four lines and nothing else, however many visitors messaged in the same window. For the first two, read `msgIds=` off the `op=routed` line and grep one of those ids. Two greps rather than one, because the debouncer merges stanzas into a single routed payload and the two halves are keyed either side of that merge.
82
72
 
83
- Anything that failed carries the same id: the drop lines, `op=public-inbound-rejected`, `op=dispatch-rejected` and `op=spawn-failed`. Fewer lines than expected, with none of them a failure, means a step emitted nothing at all — which is what the standing audits below are for.
73
+ Anything that failed carries the same id: the drop lines and `op=spawn-failed`. Fewer lines than expected, with none of them a failure, means a step emitted nothing at all — which is what the standing audits below are for.
84
74
 
85
75
  Do not try to follow an inbound by its sender. `sender=` appears on two of these lines, and the same visitor is a LID on `op=stanza` and an E.164 on `op=routed`, so no sender-based filter spans the merge.
86
76
 
@@ -95,4 +85,4 @@ op=startup-watch state=disposed
95
85
 
96
86
  The first-run dialog is answered for the session, and the watcher that guards against an *unanswered* one drops what it saw before that answer. `bufferCleared=false` means the confirm fired and the drop did not, which is the shape that killed a resumed spawn on 2026-08-01: the answered dialog's own footer stayed in the buffer, the replayed transcript slid past it, and the spawn was blocked by a line the agent itself had written the day before.
97
87
 
98
- Three standing audits reconcile that chain rather than waiting for someone to notice. `op=public-notify-reconcile` reports `gap`, `relayGap` and `answerGap`, each of which counts a step that a routed inbound reached and the next one did not. `op=spawn-census` reports attempts against successes. A non-zero gap, or attempts with no successes, is a fault even when nothing has been reported.
88
+ Two standing audits reconcile that chain rather than waiting for someone to notice. `op=public-notify-reconcile` reports `routed` against `answered`, so a visitor routed to an agent that never replied shows as `answerGap`. `op=spawn-census` reports attempts against successes. A non-zero gap, or attempts with no successes, is a fault even when nothing has been reported.
@@ -204,6 +204,12 @@ Use standard 5-field cron syntax: `minute hour day-of-month month day-of-week`.
204
204
  | `0 0 1 * *` | First of each month at midnight |
205
205
  | `*/30 * * * *` | Every 30 minutes |
206
206
 
207
+ ## What an update confirms
208
+
209
+ `schedule-update` returns the properties it wrote: `Event updated: <id> — fields: description, recurrence`. Companions count — changing `recurrence` also writes `nextRun`, changing `eventStatus` also writes `suspendedInBulk`, and setting an `agentDispatch` also clears `actionPlugin`, `actionTool` and `actionArgs`. Four properties are never named: `updatedAt` and `operatorEdited` are stamps, `embedding` is a derived search vector, and `notes` is reported as `note appended` instead, and only when the caller supplied one that was not empty.
210
+
211
+ When `agentPrompt` is not among the fields, the line ends `(the instruction sent to the agent was not changed)`. That clause is the answer to "did my prompt rewrite land" — a confirmation without it means the dispatch prompt was written. Editing a meeting opens `Meeting updated:` and names the caller's own field names, not the stored `title`/`startsAt`/`endsAt`.
212
+
207
213
  ## Skip next
208
214
 
209
215
  For recurring events, `schedule-update` with `skipNext: true` advances `nextRun` by one cycle without triggering. Use when the user says "skip tomorrow's briefing" or similar.
@@ -7,6 +7,7 @@ import { scheduleList } from "./tools/schedule-list.js";
7
7
  import { scheduleGet } from "./tools/schedule-get.js";
8
8
  import { scheduleRuns } from "./tools/schedule-runs.js";
9
9
  import { scheduleUpdate, EVENT_STATUSES } from "./tools/schedule-update.js";
10
+ import { describeUpdate } from "./lib/update-summary.js";
10
11
  import { scheduleCancel } from "./tools/schedule-cancel.js";
11
12
  import { scheduleExportIcs } from "./tools/schedule-export-ics.js";
12
13
  import { scheduleImportIcs } from "./tools/schedule-import-ics.js";
@@ -328,11 +329,14 @@ eagerTool(server, "schedule-update", "Update an event's properties or append a n
328
329
  if (!accountId)
329
330
  return refuseNoAccount("schedule-update");
330
331
  try {
331
- await scheduleUpdate({ ...params, accountId });
332
+ const written = await scheduleUpdate({ ...params, accountId });
332
333
  if (params.skipNext) {
333
334
  return { content: [{ type: "text", text: `Skipped next occurrence of event: ${params.eventId}` }] };
334
335
  }
335
- return { content: [{ type: "text", text: `Event updated: ${params.eventId}` }] };
336
+ // Task 2362 name the properties written. The flat `Event updated: <id>` this replaced read
337
+ // identically for a description edit and for a dispatch-prompt rewrite, and an agent read it
338
+ // as proof its own successful rewrite had not landed.
339
+ return { content: [{ type: "text", text: describeUpdate(params.eventId, written) }] };
336
340
  }
337
341
  catch (err) {
338
342
  return {